0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 18:32:41 +00:00

Finished stats and brew lookup on admin panel

This commit is contained in:
Scott Tolksdorf
2018-12-03 17:32:04 -05:00
parent 4241052952
commit 7656e53606
14 changed files with 308 additions and 394 deletions

View File

@@ -1,38 +1,35 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
const BrewCleanup = require('./brewCleanup/brewCleanup.jsx');
const BrewLookup = require('./brewLookup/brewLookup.jsx');
const Stats = require('./stats/stats.jsx');
const Admin = createClass({
getDefaultProps : function() {
return {
url : '',
admin_key : '',
homebrews : [],
adminKey : ''
};
},
render : function(){
return (
<div className='admin'>
<header>
<div className='container'>
<i className='fa fa-rocket' />
naturalcrit admin
</div>
</header>
return <div className='admin'>
<header>
<div className='container'>
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
<i className='fa fa-rocket' />
homebrewery admin
</div>
</header>
<div className='container'>
<Stats adminKey={this.props.adminKey} />
<hr />
<BrewLookup adminKey={this.props.adminKey} />
<hr />
<BrewCleanup adminKey={this.props.adminKey} />
</div>
);
</div>
}
});

View File

@@ -36,4 +36,60 @@ body{
}
}
}
}
/*.homebrewAdmin{
margin-bottom: 80px;
.brewTable{
table{
th{
padding : 10px;
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
&.preview{
position : relative;
&:hover{
.content{
display : block;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
}
}
}
.deleteButton{
cursor: pointer;
}
button.clearOldButton{
float : right;
}
}
*/

View File

@@ -0,0 +1,19 @@
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const BrewCleanup = createClass({
displayName : 'BrewCleanup',
getDefaultProps(){
return {
};
},
render(){
return <div className='BrewCleanup'>
BrewCleanup Component Ready.
</div>;
}
});
module.exports = BrewCleanup;

View File

@@ -0,0 +1,3 @@
.BrewCleanup{
}

View File

@@ -0,0 +1,86 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const Moment = require('moment');
const BrewLookup = createClass({
getDefaultProps() {
return {
adminKey : '',
};
},
getInitialState() {
return {
query : '',
foundBrew : null,
searching : false,
error : null
};
},
handleChange(e){
this.setState({ query : e.target.value });
},
lookup(){
this.setState({ searching: true, error: null });
request.get(`/admin/lookup/${this.state.query}`)
.query({ admin_key: this.props.adminKey })
.then((res)=> this.setState({foundBrew : res.body}))
.catch((err)=>this.setState({ error : err }))
.finally(()=>this.setState({ searching : false }))
},
renderFoundBrew(){
const brew = this.state.foundBrew;
return <div className='foundBrew'>
<dl>
<dt>Title</dt>
<dd>{brew.title}</dd>
<dt>Authors</dt>
<dd>{brew.authors.join(', ')}</dd>
<dt>Edit Link</dt>
<dd><a href={`/edit/${brew.editId}`} target='_blank' rel='noopener noreferrer'>/edit/{brew.editId}</a></dd>
<dt>Share Link</dt>
<dd><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>/share/{brew.shareId}</a></dd>
<dt>Last Updated</dt>
<dd>{Moment(brew.updatedAt).fromNow()}</dd>
<dt>Num of Views</dt>
<dd>{brew.views}</dd>
</dl>
</div>;
},
render(){
return <div className='brewLookup'>
<h2>Brew Lookup</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
<button onClick={this.lookup}>
<i className={cx('fa', {
'fa-search' : !this.state.searching,
'fa-spin fa-spinner' : this.state.searching,
})} />
</button>
{this.state.error
&& <div className='error'>{this.state.error}</div>
}
{this.state.foundBrew
? this.renderFoundBrew()
: <div className='noBrew'>No brew found.</div>
}
</div>;
}
});
module.exports = BrewLookup;

View File

@@ -0,0 +1,30 @@
.brewLookup{
input{
height : 33px;
margin-bottom : 20px;
padding : 0px 10px;
font-family : monospace;
}
button{
vertical-align : middle;
height : 37px;
}
dl{
@maxItemWidth : 132px;
dt{
float : left;
clear : left;
width : @maxItemWidth;
text-align : right;
&::after {
content: " : ";
}
}
dd{
height : 1em;
margin-left : @maxItemWidth + 6px;
padding : 0 0 0.5em 0;
}
}
}

View File

@@ -1,68 +0,0 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const Moment = require('moment');
const BrewLookup = createClass({
getDefaultProps : function() {
return {
adminKey : '',
};
},
getInitialState : function() {
return {
query : '',
resultBrew : null,
searching : false
};
},
handleChange : function(e){
this.setState({
query : e.target.value
});
},
lookup : function(){
this.setState({ searching: true });
request.get(`/admin/lookup/${this.state.query}`)
.query({ admin_key: this.props.adminKey })
.end((err, res)=>{
this.setState({
searching : false,
resultBrew : (err ? null : res.body)
});
});
},
renderFoundBrew : function(){
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
const brew = this.state.resultBrew;
return <div className='brewRow'>
<div>{brew.title}</div>
<div>{brew.authors.join(', ')}</div>
<div><a href={`/edit/${brew.editId}`} target='_blank' rel='noopener noreferrer'>/edit/{brew.editId}</a></div>
<div><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>/share/{brew.shareId}</a></div>
<div>{Moment(brew.updatedAt).fromNow()}</div>
<div>{brew.views}</div>
</div>;
},
render : function(){
return <div className='brewLookup'>
<h1>Brew Lookup</h1>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
<button onClick={this.lookup}><i className='fa fa-search'/></button>
{this.renderFoundBrew()}
</div>;
}
});
module.exports = BrewLookup;

View File

@@ -1,8 +0,0 @@
.brewLookup{
height : 200px;
input{
height : 33px;
padding : 0px 10px;
margin-bottom: 20px;
}
}

View File

@@ -1,66 +0,0 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const BrewSearch = createClass({
getDefaultProps : function() {
return {
admin_key : ''
};
},
getInitialState : function() {
return {
searchTerm : '',
brew : null,
};
},
search : function(){
request.get(`/homebrew/api/search?id=${this.state.searchTerm}`)
.query({
admin_key : this.props.admin_key,
})
.end((err, res)=>{
console.log(err, res, res.body.brews[0]);
this.setState({
brew : res.body.brews[0],
});
});
},
handleChange : function(e){
this.setState({
searchTerm : e.target.value
});
},
handleSearchClick : function(){
this.search();
},
renderBrew : function(){
if(!this.state.brew) return null;
return <div className='brew'>
<div>Edit id : {this.state.brew.editId}</div>
<div>Share id : {this.state.brew.shareId}</div>
</div>;
},
render : function(){
return <div className='search'>
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
<button onClick={this.handleSearchClick}>Search</button>
{this.renderBrew()}
</div>;
},
});
module.exports = BrewSearch;

View File

@@ -1,170 +0,0 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const Moment = require('moment');
const BrewLookup = require('./brewLookup/brewLookup.jsx');
const HomebrewAdmin = createClass({
getDefaultProps : function() {
return {
admin_key : ''
};
},
getInitialState : function() {
return {
page : 0,
count : 20,
brewCache : {},
total : 0,
};
},
fetchBrews : function(page){
request.get('/api/search')
.query({
admin_key : this.props.admin_key,
count : this.state.count,
page : page
})
.end((err, res)=>{
if(err || !res.body || !res.body.brews) return;
const newCache = _.extend({}, this.state.brewCache);
newCache[page] = res.body.brews;
this.setState({
brewCache : newCache,
total : res.body.total,
count : res.body.count
});
});
},
componentDidMount : function() {
this.fetchBrews(this.state.page);
},
changePageTo : function(page){
if(!this.state.brewCache[page]){
this.fetchBrews(page);
}
this.setState({
page : page
});
},
clearInvalidBrews : function(){
request.get('/api/invalid')
.query({ admin_key: this.props.admin_key })
.end((err, res)=>{
if(!confirm(`This will remove ${res.body.count} brews. Are you sure?`)) return;
request.get('/api/invalid')
.query({ admin_key: this.props.admin_key, do_it: true })
.end((err, res)=>{
alert('Done!');
});
});
},
deleteBrew : function(brewId){
if(!confirm(`Are you sure you want to delete '${brewId}'?`)) return;
request.get(`/api/remove/${brewId}`)
.query({ admin_key: this.props.admin_key })
.end(function(err, res){
window.location.reload();
});
},
handlePageChange : function(dir){
this.changePageTo(this.state.page + dir);
},
renderPagnination : function(){
let outOf;
if(this.state.total){
outOf = `${this.state.page} / ${Math.round(this.state.total/this.state.count)}`;
}
return <div className='pagnination'>
<i className='fa fa-chevron-left' onClick={()=>this.handlePageChange(-1)}/>
{outOf}
<i className='fa fa-chevron-right' onClick={()=>this.handlePageChange(1)}/>
</div>;
},
renderBrews : function(){
const brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
return _.map(brews, (brew)=>{
return <tr className={cx('brewRow', { 'isEmpty': brew.text == 'false' })} key={brew.shareId || brew}>
<td><a href={`/edit/${brew.editId}`} target='_blank' rel='noopener noreferrer'>{brew.editId}</a></td>
<td><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>{brew.shareId}</a></td>
<td>{Moment(brew.createdAt).fromNow()}</td>
<td>{Moment(brew.updatedAt).fromNow()}</td>
<td>{Moment(brew.lastViewed).fromNow()}</td>
<td>{brew.views}</td>
<td>
<div className='deleteButton' onClick={()=>this.deleteBrew(brew.editId)}>
<i className='fa fa-trash' />
</div>
</td>
</tr>;
});
},
renderBrewTable : function(){
return <div className='brewTable'>
<table>
<thead>
<tr>
<th>Edit Id</th>
<th>Share Id</th>
<th>Created At</th>
<th>Last Updated</th>
<th>Last Viewed</th>
<th>Views</th>
</tr>
</thead>
<tbody>
{this.renderBrews()}
</tbody>
</table>
</div>;
},
render : function(){
return <div className='homebrewAdmin'>
<BrewLookup adminKey={this.props.admin_key} />
{/*
<h2>
Homebrews - {this.state.total}
</h2>
{this.renderPagnination()}
{this.renderBrewTable()}
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
Clear Old
</button>
<BrewSearch admin_key={this.props.admin_key} />
*/}
</div>;
}
});
module.exports = HomebrewAdmin;

View File

@@ -1,53 +0,0 @@
.homebrewAdmin{
margin-bottom: 80px;
.brewTable{
table{
th{
padding : 10px;
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
&.preview{
position : relative;
&:hover{
.content{
display : block;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
}
}
}
.deleteButton{
cursor: pointer;
}
button.clearOldButton{
float : right;
}
}

View File

@@ -0,0 +1,48 @@
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent');
const Stats = createClass({
displayName : 'Stats',
getDefaultProps(){
return {
adminKey : ''
};
},
getInitialState(){
return {
stats : {
totalBrews : 0
},
fetching : false
}
},
componentDidMount(){
this.fetchStats();
},
fetchStats(){
this.setState({ fetching : true})
request.get('/admin/stats')
.query({ admin_key : this.props.adminKey })
.then((res)=> this.setState({ stats : res.body }))
.finally(()=>this.setState({fetching : false}));
},
render(){
return <div className='Stats'>
<h2> Stats </h2>
<dl>
<dt>Total Brew Count</dt>
<dd>{this.state.stats.totalBrews}</dd>
</dl>
{this.state.fetching
&& <div className='pending'><i className='fa fa-spin fa-spinner' /></div>
}
</div>;
}
});
module.exports = Stats;

View File

@@ -0,0 +1,28 @@
.Stats{
position : relative;
.pending{
position : absolute;
top : 0px;
left : 0px;
height : 100%;
width : 100%;
background-color : rgba(238,238,238, 0.5);
}
dl{
@maxItemWidth : 132px;
dt{
float : left;
clear : left;
width : @maxItemWidth;
text-align : right;
&::after {
content: " : ";
}
}
dd{
margin : 0 0 0 @maxItemWidth + 10px;
padding : 0 0 0.5em 0;
}
}
}