From 3ff81633a779e0330d324916e0bd04adeb732613 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 11 Jan 2020 23:53:42 +0000 Subject: [PATCH] Use item list for members and add member detail view --- apiserver/apiserver/api/serializers.py | 4 +- apiserver/apiserver/api/views.py | 10 +-- webclient/src/App.js | 24 +++++-- webclient/src/Cards.js | 6 +- webclient/src/Classes.js | 6 +- webclient/src/Home.js | 14 ++-- webclient/src/Members.js | 98 +++++++++++++++++--------- webclient/src/Transactions.js | 6 +- webclient/src/light.css | 4 +- webclient/src/utils.js | 9 +++ 10 files changed, 118 insertions(+), 63 deletions(-) diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py index aa7da3a..fbfe4cc 100644 --- a/apiserver/apiserver/api/serializers.py +++ b/apiserver/apiserver/api/serializers.py @@ -60,11 +60,11 @@ class UserSerializer(serializers.ModelSerializer): # member viewing member list or other member class OtherMemberSerializer(serializers.ModelSerializer): q = serializers.CharField(write_only=True, max_length=64) - seq = serializers.IntegerField(write_only=True, ) + seq = serializers.IntegerField(write_only=True) class Meta: model = models.Member - fields = ['q', 'seq', 'preferred_name', 'last_name', 'status', 'current_start_date', 'photo_small'] + fields = ['q', 'seq', 'id', 'preferred_name', 'last_name', 'status', 'current_start_date', 'photo_small', 'photo_large'] # member viewing himself class MemberSerializer(serializers.ModelSerializer): diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index c0a4cea..9827e5b 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -1,6 +1,6 @@ from django.contrib.auth.models import User, Group from django.db.models import Max -from rest_framework import viewsets, views, permissions +from rest_framework import viewsets, views, permissions, mixins from rest_framework.response import Response from rest_auth.registration.views import RegisterView from fuzzywuzzy import fuzz, process @@ -23,12 +23,12 @@ def gen_search_strings(): ).lower() search_strings[string] = m.id -class SearchViewSet(viewsets.ViewSet): +NUM_SEARCH_RESULTS = 10 +class SearchViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin): permission_classes = [AllowMetadata | permissions.IsAuthenticated] serializer_class = serializers.OtherMemberSerializer def get_queryset(self): - NUM_SEARCH_RESULTS = 10 queryset = models.Member.objects.all() search = self.request.data.get('q', '').lower() @@ -55,7 +55,7 @@ class SearchViewSet(viewsets.ViewSet): queryset = result_objects else: gen_search_strings() - queryset = queryset.order_by('-vetted_date')[:NUM_SEARCH_RESULTS] + queryset = queryset.order_by('-vetted_date') return queryset @@ -66,7 +66,7 @@ class SearchViewSet(viewsets.ViewSet): except ValueError: seq = 0 - queryset = self.get_queryset() + queryset = self.get_queryset()[:NUM_SEARCH_RESULTS] serializer = self.serializer_class(queryset, many=True) return Response({'seq': seq, 'results': serializer.data}) diff --git a/webclient/src/App.js b/webclient/src/App.js index 12810cd..e21627f 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom'; -import './light.css'; -import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; +import './light.css'; import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; import { requester } from './utils.js'; import { Home } from './Home.js'; import { Transactions, TransactionDetail } from './Transactions.js'; @@ -9,7 +8,7 @@ import { Cards } from './Cards.js'; import { Training } from './Training.js'; import { Courses, CourseDetail } from './Courses.js'; import { Classes, ClassDetail } from './Classes.js'; -import { Members } from './Members.js'; +import { Members, MemberDetail } from './Members.js'; import { NotFound, PleaseLogin } from './Misc.js'; import { Footer } from './Footer.js'; @@ -50,7 +49,7 @@ function App() {
-
+
@@ -63,8 +62,13 @@ function App() { to='/' /> - + + + @@ -150,6 +159,9 @@ function App() { + + + diff --git a/webclient/src/Cards.js b/webclient/src/Cards.js index b92cb70..e29d4ef 100644 --- a/webclient/src/Cards.js +++ b/webclient/src/Cards.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom'; import './light.css'; import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; -import { requester } from './utils.js'; +import { BasicTable, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; export function Cards(props) { @@ -39,7 +39,7 @@ export function Cards(props) { : - + Number: @@ -58,7 +58,7 @@ export function Cards(props) { {cardStatus(card)} -
+ :

No cards yet! Ask a director for one after you are vetted.

} diff --git a/webclient/src/Classes.js b/webclient/src/Classes.js index fc50958..450d487 100644 --- a/webclient/src/Classes.js +++ b/webclient/src/Classes.js @@ -3,7 +3,7 @@ import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-r import './light.css'; import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; import moment from 'moment'; -import { requester } from './utils.js'; +import { BasicTable, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; function ClassTable(props) { @@ -115,7 +115,7 @@ export function ClassDetail(props) {
Class Details
- + Name: @@ -150,7 +150,7 @@ export function ClassDetail(props) { {clazz.student_count} -
+
Attendance
diff --git a/webclient/src/Home.js b/webclient/src/Home.js index 36bd4f0..a10a306 100644 --- a/webclient/src/Home.js +++ b/webclient/src/Home.js @@ -2,10 +2,10 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom'; import './light.css'; import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; -import { staticUrl, requester } from './utils.js'; +import { BasicTable, staticUrl, requester } from './utils.js'; import { LoginForm, SignupForm } from './LoginSignup.js'; -function DetailsForm(props) { +function SignupDetailsForm(props) { const member = props.user.member; const [input, setInput] = useState({ preferred_name: member.preferred_name, @@ -110,7 +110,7 @@ function MemberInfo(props) {
Details
- + Expiry: @@ -137,10 +137,10 @@ function MemberInfo(props) { {lastCard && lastCard.card_number || 'None'} -
+
Latest Transactions
- + {lastTrans.length ? lastTrans.map((x, i) => @@ -156,7 +156,7 @@ function MemberInfo(props) { None } -
+
); }; @@ -172,7 +172,7 @@ export function Home(props) { user.member.set_details ? : - + :
diff --git a/webclient/src/Members.js b/webclient/src/Members.js index 87b2cd8..669f1f1 100644 --- a/webclient/src/Members.js +++ b/webclient/src/Members.js @@ -1,9 +1,9 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom'; import './light.css'; -import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Input, Menu, Message, Segment, Table } from 'semantic-ui-react'; +import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Input, Item, Menu, Message, Segment, Table } from 'semantic-ui-react'; import moment from 'moment'; -import { staticUrl, requester } from './utils.js'; +import { BasicTable, staticUrl, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; export function Members(props) { @@ -48,36 +48,22 @@ export function Members(props) { {members ? - - - - - Name - Status - Member Since - - - - - {members.results.length ? - members.results.map((x, i) => - - - - - {x.preferred_name} {x.last_name} - {x.status} - {x.current_start_date} - - ) - : - No Results - } - -
+ + {members.results.length ? + members.results.map((x, i) => + + + + {x.preferred_name} {x.last_name} + Status: {x.status || 'Unknown'} + Joined: {x.current_start_date || 'Unknown'} + + + ) + : +

No Results

+ } +
:

Loading...

} @@ -86,3 +72,51 @@ export function Members(props) { ); }; +export function MemberDetail(props) { + const [member, setMember] = useState(false); + const [error, setError] = useState(false); + const { token } = props; + const { id } = useParams(); + + useEffect(() => { + requester('/search/'+id+'/', 'GET', token) + .then(res => { + setMember(res); + }) + .catch(err => { + console.log(err); + setError(true); + }); + }, []); + + return ( + + {!error ? + member ? +
+
{member.preferred_name} {member.last_name}
+ + + + + + + Status: + {member.status || 'Unknown'} + + + Joined: + {member.current_start_date || 'Unknown'} + + + +
+ : +

Loading...

+ : + + } +
+ ); +}; + diff --git a/webclient/src/Transactions.js b/webclient/src/Transactions.js index 1367d6b..f17a949 100644 --- a/webclient/src/Transactions.js +++ b/webclient/src/Transactions.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom'; import './light.css'; import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; -import { requester } from './utils.js'; +import { BasicTable, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; export function Transactions(props) { @@ -55,7 +55,7 @@ export function TransactionDetail(props) {
Transaction Receipt
- + Date: @@ -90,7 +90,7 @@ export function TransactionDetail(props) { {t.memo} -
+
: diff --git a/webclient/src/light.css b/webclient/src/light.css index b026516..1ee4eac 100644 --- a/webclient/src/light.css +++ b/webclient/src/light.css @@ -15,12 +15,12 @@ body { margin: 0; } -.header { +.hero { padding-top: 1.5rem; margin-bottom: 1.5rem; } -.header .logo-long { +.hero .logo-long { max-width: 100%; height: 2rem; display: block; diff --git a/webclient/src/utils.js b/webclient/src/utils.js index 254b454..74d9f8d 100644 --- a/webclient/src/utils.js +++ b/webclient/src/utils.js @@ -1,7 +1,16 @@ +import React, { useState, useEffect } from 'react'; +import { Table } from 'semantic-ui-react'; + export const siteUrl = window.location.protocol + '//' + window.location.hostname; export const apiUrl = window.location.protocol + '//api.' + window.location.hostname; export const staticUrl = window.location.protocol + '//static.' + window.location.hostname; +export const BasicTable = (props) => ( + + {props.children} +
+); + export const requester = (route, method, token, data) => { let options = {headers: {}};