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 (
+
+
+
+
+
-
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 (
+
+
+
+
+
+
+
+
+ {success &&
Success!
}
+
+ Submit
+
+
+
+
+
+ {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 ?
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
- {success && Success!
}
-
- Submit
-
-
- :
- Loading...
- }
+ {success && Success!
}
+
+ Submit
+
+
);
};
@@ -123,66 +308,60 @@ export function AdminMemberInfo(props) {
return (
- {member ?
-
-
-
-
-
-
- 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...
- }
+
+
+
+
+
+ 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) {
-
+
+
+
+
{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;