조민지

feat: 킥보드 및 사용자 검색 기능 구현

......@@ -12,55 +12,51 @@ const UserDataKey = {
rental_fee: '대여 금액',
};
class KickboardHistoryTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
function timestampToString(stamp) {
if (!stamp) return '';
const date = stamp.split('T')[0];
const time = stamp.split('T')[1].split('.')[0];
componentDidMount() {
fetch(`http://1.201.143.67:5901/kickboard/rent/${this.props.kbId}`)
.then(r => r.json())
.then(d => {
if(d.data && d.data.length) this.setState({data: d.data});
})
.catch(err => console.log(err));
}
return `${date} ${time}`;
}
render() {
return (
<Card
title={`${this.props.kbId}번 킥보드 사용자 히스토리`}
ctTableFullWidth
ctTableResponsive
content={
<Table striped hover>
<thead>
<tr>
{Object.values(UserDataKey).map((prop, key) => {
return <th key={key}>{prop}</th>;
})}
</tr>
</thead>
<tbody>
{this.state.data.map((data, idx) => (
const KickboardHistoryTable = (props) => {
const {kbId, historyData} = props;
return (
<Card
title={`${kbId}번 킥보드 사용자 히스토리`}
ctTableFullWidth
ctTableResponsive
content={
<Table striped hover>
<thead>
<tr>
{Object.values(UserDataKey).map((prop, key) => {
return <th key={key}>{prop}</th>;
})}
</tr>
</thead>
<tbody>
{historyData.map((data, idx) => {
const {user_id, rent_datetime, return_datetime, rental_time, rental_distance, rental_fee} = data;
return (
<tr key={idx}>
<td>{data.user_id}</td>
<td>{data.rent_datetime}</td>
<td>{data.return_datetime}</td>
<td>{data.rental_time}</td>
<td>{data.rental_distance}</td>
<td>{data.rental_fee}</td>
<td>{user_id}</td>
<td>{timestampToString(rent_datetime)}</td>
<td>{timestampToString(return_datetime)}</td>
<td>{rental_time}</td>
<td>{rental_distance}km</td>
<td>{rental_fee}</td>
</tr>
))}
</tbody>
</Table>
}
/>
)
}
);
})}
</tbody>
</Table>
}
/>
)
};
export default KickboardHistoryTable;
......
......@@ -61,7 +61,7 @@ const kickboardDataKey = {
// "updated_datetime":"2020-06-12T10:00:28.715Z",
// "battery":72
function makeTimestampToString(stamp) {
function timestampToString(stamp) {
if (!stamp) return '';
const date = stamp.split('T')[0];
const time = stamp.split('T')[1].split('.')[0];
......@@ -69,32 +69,16 @@ function makeTimestampToString(stamp) {
return `${date} ${time}`;
}
class KickboardStatusCard extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
};
}
const KickboardStatusCard = (props) => {
const {kbId, kbData} = props;
const { battery, coordinates, states, is_good_posture, total_driven_distance,
total_driven_time, is_good_gps_signal, connected, updated_coordinates_datetime,
connection_updated_datetime, model_name, updated_datetime } = kbData;
const getStats = `마지막 업데이트 ${moment().format("YYYY/MM/DD hh:mm")}`;
componentDidMount() {
fetch(`http://1.201.143.67:5901/kickboard/${this.props.kbId}`)
.then(r => r.json())
.then(d => {
if(d.data && d.data.length) this.setState({data: d.data[0]});
})
.catch(err => console.log(err));
}
render() {
const getStats = `마지막 업데이트 ${moment().format("YYYY/MM/DD hh:mm")}`;
const { battery, coordinates, states, is_good_posture, total_driven_distance,
total_driven_time, is_good_gps_signal, connected, updated_coordinates_datetime,
connection_updated_datetime, model_name, updated_datetime } = this.state.data;
return (
<Card
title={`${this.props.kbId}번 킥보드`}
return (
<Card
title={`${kbId}번 킥보드`}
stats={getStats}
statsIcon="fa fa-history"
content={
......@@ -117,16 +101,15 @@ class KickboardStatusCard extends React.Component {
<KickboardData>{kickboardDataKey.total_driven_time} : {total_driven_time}</KickboardData>
<KickboardData>{kickboardDataKey.is_good_gps_signal} : {is_good_gps_signal ? '양호' : '불량'}</KickboardData>
<KickboardData>{kickboardDataKey.connected} : {connected ? '연결됨' : '연결되지 않음'}</KickboardData>
<KickboardData>{kickboardDataKey.updated_coordinates_datetime} : {makeTimestampToString(updated_coordinates_datetime)}</KickboardData>
<KickboardData>{kickboardDataKey.connection_updated_datetime} : {makeTimestampToString(connection_updated_datetime)}</KickboardData>
<KickboardData>{kickboardDataKey.updated_coordinates_datetime} : {timestampToString(updated_coordinates_datetime)}</KickboardData>
<KickboardData>{kickboardDataKey.connection_updated_datetime} : {timestampToString(connection_updated_datetime)}</KickboardData>
<KickboardData>{kickboardDataKey.model_name} : {model_name}</KickboardData>
<KickboardData>{kickboardDataKey.updated_datetime} : {makeTimestampToString(updated_datetime)}</KickboardData>
<KickboardData>{kickboardDataKey.updated_datetime} : {timestampToString(updated_datetime)}</KickboardData>
</KickboardDataList>
</Row>
}
/>
);
}
/>
);
};
export default KickboardStatusCard;
\ No newline at end of file
......
import React from "react";
import React, {useRef} from "react";
import styled from "styled-components";
const SearchButtonWrapper = styled.div`
......@@ -30,11 +30,25 @@ const ApplyButton = styled.div`
const SearchButton = () => {
const SearchButton = (props) => {
const {setKbId} = props;
const inputEl = useRef(null);
const searchKickboard = (e) => {
const searchText = e.target.value;
fetch(`http://1.201.143.67:5901/kickboard/${searchText}`)
.then(r => r.json())
.then(d => {
if(d.success && d.data.length) setKbId();
else window.alert('해당 번호의 킥보드가 존재하지 않습니다!')
})
.catch(err => console.log(err));
};
return (
<SearchButtonWrapper>
<SearchInput type="text" className="form-control" placeholder={"킥보드 번호로 검색하기"}/>
<ApplyButton><span>검색</span></ApplyButton>
<SearchInput ref={inputEl} type="text" className="form-control" placeholder={"킥보드 번호로 검색하기"}/>
<ApplyButton onClick={(e) => searchKickboard(e)}><span>검색</span></ApplyButton>
</SearchButtonWrapper>
);
};
......
......@@ -27,17 +27,11 @@ class Sidebar extends Component {
backgroundColor: 'rgb(217,94,45)'
};
console.log(this.props.color);
return (
<div
id="sidebar"
className="sidebar"
>
{this.props.hasImage ? (
<div className="sidebar-background" style={sidebarBackground} />
) : (
null
)}
<div className="logo">
<a
href="https://www.creative-tim.com?ref=lbd-sidebar"
......
......@@ -23,7 +23,6 @@ import Button from "components/CustomButton/CustomButton.jsx";
export class Tasks extends Component {
handleCheckbox = event => {
const target = event.target;
console.log(event.target);
this.setState({
[target.name]: target.checked
});
......
import React, {useRef} from "react";
import styled from "styled-components";
import {Col, Row} from "react-bootstrap";
import Card from "components/Card/Card.jsx";
const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
color: #333333;
text-align: center;
`;
const EmptyImage = styled.img`
max-width: 100%;
height: auto !important;
`;
const SearchButton = (props) => {
return (
<Card
content={
<Row>
<Col md={8} mdOffset={2}>
<EmptyImage src="https://raw.githubusercontent.com/devSoyoung/whale-extension-keycrab/master/images/empty.png" alt="empty"/>
</Col>
<Col md={12}>
<Wrapper>
{
Number(props.userId)<0
? <div>검색 결과가 존재하지 않습니다.</div>
: <div>전화번호, 이메일, 사용자ID를 통해<br/>사용자를 검색해보세요!</div>
}
</Wrapper>
</Col>
</Row>
}
/>
);
};
export default SearchButton;
import React from "react";
import React, {useRef, useState} from "react";
import styled from "styled-components";
const SearchButtonWrapper = styled.div`
......@@ -8,7 +8,7 @@ const SearchButtonWrapper = styled.div`
`;
const SearchInput = styled.input`
width: 60%;
width: 55%;
`;
const ApplyButton = styled.div`
......@@ -29,22 +29,58 @@ const ApplyButton = styled.div`
`;
const SelectBox = styled.select`
width: 17%;
width: 22%;
color: #333333;
border-color: #E3E3E3;
border-radius: 4px;
`;
const searchTypeKR = {
phone_number: '전화번호',
email: '이메일',
user_id: '사용자ID'
};
const SearchButton = (props) => {
const selectEl = useRef(null);
const inputEl = useRef(null);
const {setUserId} = props;
const [searchType, setSearchType] = useState('phone_number');
const searchUserData = (e) => {
e.preventDefault();
const searchText = inputEl.current.value;
if(searchType === 'user_id') {
fetch(`http://1.201.143.67:5901/user/${searchType}`)
.then(r => r.json())
.then(d => {
if(d.success) setUserId(searchType);
else setUserId('-1'); // 검색결과 X
})
} else {
fetch(`http://1.201.143.67:5901/user/${searchType}/${searchText}`)
.then(r => r.json())
.then(d => {
if(d.success) setUserId(d.data[0].user_id);
else setUserId('-1'); // 검색결과 X
})
}
};
const onChangeSearchOption = (e) => {
setSearchType(e.target.value)
};
const SearchButton = () => {
return (
<SearchButtonWrapper>
<SelectBox>
<option value="전화 번호">전화 번호</option>
<option value="이메일">이메일</option>
<SelectBox ref={selectEl} onChange={(e) => onChangeSearchOption(e)}>
<option value="phone_number">전화번호</option>
<option value="email">이메일</option>
<option value="user_id">사용자ID</option>
</SelectBox>
<SearchInput type="text" className="form-control" placeholder={"킥보드 번호로 검색하기"}/>
<ApplyButton><span>검색</span></ApplyButton>
<SearchInput type="text" ref={inputEl} className="form-control" placeholder={`${searchTypeKR[searchType]}로 검색하기`}/>
<ApplyButton onClick={(e) => searchUserData(e)}><span>검색</span></ApplyButton>
</SearchButtonWrapper>
);
};
......
......@@ -9,7 +9,7 @@ const UserData = styled.div`
font-size: 15px;
`;
function makeTimestampToString(stamp) {
function timestampToString(stamp) {
if (!stamp) return '';
const date = stamp.split('T')[0];
const time = stamp.split('T')[1].split('.')[0];
......@@ -56,10 +56,10 @@ const UserDataCard = (props) => {
</Row>
<Row>
<Col md={6} sm={6} xs={12}>
<UserData>가입 날짜 : {makeTimestampToString(created_datetime)}</UserData>
<UserData>가입 날짜 : {timestampToString(created_datetime)}</UserData>
</Col>
<Col md={6} sm={6} xs={12}>
<UserData>최근 로그인 날짜 : {makeTimestampToString(recent_login_datetime)}</UserData>
<UserData>최근 로그인 날짜 : {timestampToString(recent_login_datetime)}</UserData>
</Col>
</Row>
<Row>
......
......@@ -7,6 +7,14 @@ import moment from "moment";
const thArray = ['킥보드 시리얼 번호', '대여 시각', '반납 시각', '총 대여 시간', '이동 거리', '대여 금액'];
function timestampToString(stamp) {
if (!stamp) return '';
const date = stamp.split('T')[0];
const time = stamp.split('T')[1].split('.')[0];
return `${date} ${time}`;
}
const UserHistoryCard = (props) => {
const {userId, rentData} = props;
const getStats = `마지막 업데이트 ${moment().format("YYYY/MM/DD hh:mm")}`;
......@@ -14,7 +22,6 @@ const UserHistoryCard = (props) => {
return (
<Card
title={`${userId}번 사용자 킥보드 사용 히스토리`}
ctTableFullWidth
ctTableResponsive
stats={getStats}
statsIcon="fa fa-history"
......@@ -31,11 +38,11 @@ const UserHistoryCard = (props) => {
{rentData.map((data, idx) => (
<tr key={idx}>
<td>{data.serial_number}</td>
<td>{data.rent_datetime}</td>
<td>{data.return_datetime}</td>
<td>{data.rental_time}</td>
<td>{data.rental_distance}</td>
<td>{data.rental_fee}</td>
<td>{timestampToString(data.rent_datetime)}</td>
<td>{timestampToString(data.return_datetime)}</td>
<td>{data.rental_time}</td>
<td>{data.rental_distance}km</td>
<td>{data.rental_fee}</td>
</tr>
))}
</tbody>
......
......@@ -8,6 +8,23 @@ import SearchButton from '../components/Kickboard/SearchButton';
const Kickboard = () => {
const [kbId, setKbId] = useState('000165');
const [kbData, setKbData] = useState({});
const [historyData, setHistoryData] = useState([]);
useEffect(() => {
fetch(`http://1.201.143.67:5901/kickboard/${kbId}`)
.then(r => r.json())
.then(d => {
if(d.data && d.data.length) setKbData(d.data[0]);
fetch(`http://1.201.143.67:5901/kickboard/rent/${kbId}`)
.then(r => r.json())
.then(d => {
if(d.data && d.data.length) setHistoryData(d.data);
})
})
.catch(err => console.log(err));
},[kbId]);
// 여기 API 요청
return (
......@@ -15,7 +32,7 @@ const Kickboard = () => {
<Grid fluid>
<Row>
<Col md={4} mdOffset={8} sm={3} smOffset={9} style={{marginBottom:15}}>
<SearchButton/>
<SearchButton setKbId={setKbId}/>
</Col>
</Row>
<Row>
......@@ -23,12 +40,12 @@ const Kickboard = () => {
<GoogleMapCard/>
</Col>
<Col md={6}>
<KickboardStatusCard kbId={kbId}/>
<KickboardStatusCard kbId={kbId} kbData={kbData}/>
</Col>
</Row>
<Row>
<Col md={12}>
<KickboardHistoryTable kbId={kbId}/>
<KickboardHistoryTable kbId={kbId} historyData={historyData}/>
</Col>
</Row>
</Grid>
......
......@@ -3,9 +3,10 @@ import { Grid, Row, Col, Table } from "react-bootstrap";
import UserDataCard from '../components/UserHistory/UserDataCard';
import UserHistoryCard from '../components/UserHistory/UserHistoryCard';
import SearchButton from "../components/UserHistory/SearchButton";
import Fallback from '../components/UserHistory/Fallback';
const UserHistory = () => {
const [userId, setUserId] = useState(7575);
const [userId, setUserId] = useState('0');
const [userData, setUserData] = useState({});
const [rentData, setRentData] = useState([]);
......@@ -14,37 +15,50 @@ const UserHistory = () => {
},[]);
useEffect(() => {
if(Number(userId)<0) return;
fetch(`http://1.201.143.67:5901/user/${userId}`)
.then(r => r.json())
.then(d => {
console.log('userData',d.data[0])
if(!d.success) setUserId('0'); // 유효하지 않은 userId인 경우
if(d.data && d.data.length) setUserData(d.data[0]);
fetch(`http://1.201.143.67:5901/user/rent/${userId}`)
.then(r => r.json())
.then(d => {
console.log('rentData',d.data);
if(d.data && d.data.length) setRentData(d.data);
})
})
.catch(err => console.log(err)); },[userId]);
.catch(err => console.log(err));
},[userId]);
return (
<div className="content">
<Grid fluid>
<Row>
<Col md={4} mdOffset={8} sm={5} smOffset={7} style={{marginBottom:15}}>
<SearchButton/>
</Col>
</Row>
<Row>
<Col md={12}>
<UserDataCard userId={userId} userData={userData}/>
</Col>
<Col md={12}>
<UserHistoryCard userId={userId} rentData={rentData}/>
<SearchButton setUserId={setUserId}/>
</Col>
</Row>
{
Number(userId)>0
? (
<Row>
<Col md={12}>
<UserDataCard userId={userId} userData={userData}/>
</Col>
<Col md={12}>
<UserHistoryCard userId={userId} rentData={rentData}/>
</Col>
</Row>
)
: (
<Row>
<Col md={8} mdOffset={2}>
<Fallback userId={userId}/>
</Col>
</Row>
)
}
</Grid>
</div>
);
......