diff --git a/apiserver/apiserver/api/models.py b/apiserver/apiserver/api/models.py index b21f7e5..da39e45 100644 --- a/apiserver/apiserver/api/models.py +++ b/apiserver/apiserver/api/models.py @@ -55,7 +55,7 @@ class Card(models.Model): user = models.ForeignKey(User, related_name='cards', blank=True, null=True, on_delete=models.SET_NULL) member_id = models.IntegerField(blank=True, null=True) - card_number = models.CharField(max_length=16, blank=True, null=True) + card_number = models.CharField(unique=True, max_length=16, blank=True, null=True) notes = models.TextField(blank=True, null=True) last_seen_at = models.DateField(default=date.today, blank=True, null=True) active_status = models.CharField(max_length=32, blank=True, null=True) diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py index c579e72..d75acfb 100644 --- a/apiserver/apiserver/api/serializers.py +++ b/apiserver/apiserver/api/serializers.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User, Group from django.shortcuts import get_object_or_404 from rest_framework import serializers from rest_framework.exceptions import ValidationError +from rest_framework.validators import UniqueValidator from rest_auth.registration.serializers import RegisterSerializer from rest_auth.serializers import UserDetailsSerializer from uuid import uuid4 @@ -51,15 +52,6 @@ class UserEmailField(serializers.ModelField): -class AdminCardSerializer(serializers.ModelSerializer): - card_number = serializers.CharField() - class Meta: - model = models.Card - fields = '__all__' - read_only_fields = ['last_seen_at'] - - - # member viewing other members class OtherMemberSerializer(serializers.ModelSerializer): @@ -177,7 +169,10 @@ class CardSerializer(serializers.ModelSerializer): # admin viewing member details class AdminCardSerializer(CardSerializer): - card_number = serializers.CharField() + card_number = serializers.CharField(validators=[UniqueValidator( + queryset=models.Card.objects.all(), + message='Card number already exists.' + )]) member_id = serializers.IntegerField() active_status = serializers.ChoiceField(['card_blocked', 'card_inactive', 'card_member_blocked', 'card_active']) class Meta: diff --git a/webclient/src/Account.js b/webclient/src/Account.js index bf55598..d0c95fe 100644 --- a/webclient/src/Account.js +++ b/webclient/src/Account.js @@ -98,27 +98,33 @@ export function AccountForm(props) { onChange: handleChange, value: input[name] || '', error: error[name], + ...(input[name] ? {} : {icon: 'edit'}), }); return (
Member Details
- + + + + + - setInput({ ...input, [v.name]: v.value }); + const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); + const handleChange = (e) => handleValues(e, e.currentTarget); + const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); + + const handleSubmit = (e) => { + setLoading(true); + setSuccess(false); + const data = { ...input, member_id: props.result.member.id }; + requester('/cards/'+id+'/', 'PUT', props.token, data) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + setInput(res); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + const handleDelete = (e) => { + e.preventDefault(); + + requester('/cards/'+id+'/', 'DELETE', props.token) + .then(res => { + setInput(false); + }) + .catch(err => { + console.log(err); + }); + }; + + const makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + const statusOptions = [ + { key: '0', text: 'Card Active', value: 'card_active' }, + { key: '1', text: 'Card Blocked', value: 'card_blocked' }, + { key: '2', text: 'Card Inactive', value: 'card_inactive' }, + { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, + ]; + + return ( + input ? + + + + + + + + + {success ? 'Saved.' : 'Save'} + + + + Delete + + + + + Notes: {input.notes || 'None'} + + + : + + Deleted card: {props.card.card_number} + + ); +}; + +export function AdminMemberCards(props) { + const cards = props.result.cards; + const [input, setInput] = useState({ active_status: 'card_active' }); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const { id } = useParams(); + + const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); + const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); + const handleChange = (e) => handleValues(e, e.currentTarget); + const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); + + const handleSubmit = (e) => { + setLoading(true); + setSuccess(false); + const data = { ...input, member_id: props.result.member.id }; + requester('/cards/', 'POST', props.token, data) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + props.setResult({ ...props.result, cards: [...cards, res] }); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + const makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + const statusOptions = [ + { key: '0', text: 'Card Active', value: 'card_active' }, + { key: '1', text: 'Card Blocked', value: 'card_blocked' }, + { key: '2', text: 'Card Inactive', value: 'card_inactive' }, + { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, + ]; + + return ( +
+
Edit Member Cards
+
+
Add a Card
+ + + + + + + + {success &&

Success!

} + + Submit + +
+ +
Current Cards
+ + {cards.length ? + cards.map((x, i) => + + ) + : +

None

+ } +
+ ); +}; + export function AdminMemberForm(props) { const [input, setInput] = useState(props.result.member); const [error, setError] = useState(false); @@ -42,78 +231,74 @@ export function AdminMemberForm(props) { return (
- {input ? -
-
Edit Member Details
+ +
Edit Member Details
- + - + - + - + - + + + + + + + - + + + - - - - - - - - - - - - - - + + + + - {success &&

Success!

} - - Submit - - - : -

Loading...

- } + {success &&

Success!

} + + Submit + +
); }; @@ -123,66 +308,60 @@ export function AdminMemberInfo(props) { return (
- {member ? -
-
Admin Details
- - - - - Name: - {member.first_name} {member.last_name} - - - Status: - {member.status} - - - - Phone: - {member.phone} - - - - Address: - {member.street_address} - - - City: - {member.city} - - - Postal: - {member.postal_code} - - - - Minor: - {member.is_minor ? 'Yes' : 'No'} - - {member.is_minor && - Birthdate: - {member.birthdate} - } - {member.is_minor && - Guardian: - {member.guardian_name} - } - - - Emergency Contact Name: - {member.emergency_contact_name || 'None'} - - - Emergency Contact Phone: - {member.emergency_contact_phone || 'None'} - - - -
- : -

Loading...

- } +
Admin Details
+ + + + + Name: + {member.first_name} {member.last_name} + + + Status: + {member.status} + + + + Phone: + {member.phone} + + + + Address: + {member.street_address} + + + City: + {member.city} + + + Postal: + {member.postal_code} + + + + Minor: + {member.is_minor ? 'Yes' : 'No'} + + {member.is_minor && + Birthdate: + {member.birthdate} + } + {member.is_minor && + Guardian: + {member.guardian_name} + } + + + Emergency Contact Name: + {member.emergency_contact_name || 'None'} + + + Emergency Contact Phone: + {member.emergency_contact_phone || 'None'} + + +
); }; diff --git a/webclient/src/LoginSignup.js b/webclient/src/LoginSignup.js index 22dc661..bf7578c 100644 --- a/webclient/src/LoginSignup.js +++ b/webclient/src/LoginSignup.js @@ -84,18 +84,22 @@ export function SignupForm(props) {
Sign Up
- - + + + + {response ? - + {response.results.length ? response.results.map((x, i) => @@ -130,6 +130,10 @@ export function MemberDetail(props) { + {isAdmin(user) && + + } + :

Loading...

diff --git a/webclient/src/utils.js b/webclient/src/utils.js index dff642a..8a5fdc3 100644 --- a/webclient/src/utils.js +++ b/webclient/src/utils.js @@ -33,6 +33,11 @@ export const requester = (route, method, token, data) => { method: method, body: formData, }; + } else if (method == 'DELETE') { + options = { + ...options, + method: method, + }; } else { throw new Error('Method not supported'); } @@ -48,7 +53,7 @@ export const requester = (route, method, token, data) => { if (!response.ok) { throw customError(response); } - return response.json(); + return method === 'DELETE' ? {} : response.json(); }) .catch(error => { const code = error.data ? error.data.status : null;