From 6a7f409986be61cf0affa54134069e362c82050a Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 18 Jan 2020 03:34:55 +0000 Subject: [PATCH] Add transaction editor to UI --- apiserver/apiserver/api/serializers.py | 12 +- webclient/src/App.js | 2 +- webclient/src/Transactions.js | 300 +++++++++++++++++++++---- webclient/src/light.css | 2 +- 4 files changed, 268 insertions(+), 48 deletions(-) diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py index 911966b..102d5d6 100644 --- a/apiserver/apiserver/api/serializers.py +++ b/apiserver/apiserver/api/serializers.py @@ -82,7 +82,9 @@ class HTMLField(serializers.CharField): class TransactionSerializer(serializers.ModelSerializer): account_type = serializers.ChoiceField(['Interac', 'TD Chequing', 'Paypal', 'Dream Pmt', 'PayPal', 'Square Pmt', 'Member', 'Clearing', 'Cash']) - info_source = serializers.ChoiceField(['Web', 'DB Edit', 'System', 'Receipt or Stmt', 'Quicken Import', 'Paypal IPN', 'Auto', 'Nexus DB Bulk', 'PayPal IPN', 'IPN Trigger', 'Intranet Receipt', 'Automatic', 'Manual']) + info_source = serializers.ChoiceField(['Web', 'DB Edit', 'System', 'Receipt or Stmt', 'Quicken Import', 'Paypal IPN', 'PayPal IPN', 'Auto', 'Nexus DB Bulk', 'IPN Trigger', 'Intranet Receipt', 'Automatic', 'Manual']) + member_name = serializers.SerializerMethodField() + date = serializers.DateField() class Meta: model = models.Transaction fields = '__all__' @@ -99,6 +101,13 @@ class TransactionSerializer(serializers.ModelSerializer): validated_data['user'] = member.user return super().create(validated_data) + def get_member_name(self, obj): + if obj.user: + member = obj.user.member + else: + member = models.Member.objects.get(id=obj.member_id) + return member.preferred_name + ' ' + member.last_name + # member viewing other members @@ -309,6 +318,7 @@ class UserTrainingSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer): training = UserTrainingSerializer(many=True) member = MemberSerializer() + transactions = TransactionSerializer(many=True) class Meta: model = User diff --git a/webclient/src/App.js b/webclient/src/App.js index 7303f64..dfb495a 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -133,7 +133,7 @@ function App() { - + diff --git a/webclient/src/Transactions.js b/webclient/src/Transactions.js index df64912..7cb0b88 100644 --- a/webclient/src/Transactions.js +++ b/webclient/src/Transactions.js @@ -2,9 +2,182 @@ 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 { BasicTable, requester } from './utils.js'; +import { isAdmin, BasicTable, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; +function TransactionEditor(props) { + const { input, setInput, error } = props; + + 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 makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + const accountOptions = [ + { key: '0', text: 'Cash (CAD Lock Box)', value: 'Cash' }, + { key: '1', text: 'Interac (Email) Transfer (TD)', value: 'Interac' }, + { key: '2', text: 'Square (Credit)', value: 'Square Pmt' }, + { key: '3', text: 'Dream Payments (Debit/Credit)', value: 'Dream Pmt' }, + { key: '4', text: 'Deposit to TD (Not Interac)', value: 'TD Chequing' }, + { key: '5', text: 'PayPal', value: 'Paypal' }, + { key: '6', text: 'Member Balance / Protocash', value: 'Member' }, + { key: '7', text: 'Supense (Clearing) Acct / Membership Adjustment', value: 'Clearing' }, + ]; + + const sourceOptions = [ + { key: '0', text: 'Web', value: 'Web' }, + { key: '1', text: 'Database Edit', value: 'DB Edit' }, + { key: '2', text: 'System', value: 'System' }, + { key: '3', text: 'Receipt or Statement', value: 'Receipt or Stmt' }, + { key: '4', text: 'Quicken Import', value: 'Quicken Import' }, + { key: '5', text: 'PayPal IPN', value: 'Paypal IPN' }, + { key: '6', text: 'Auto', value: 'Auto' }, + { key: '7', text: 'Nexus Database Bulk', value: 'Nexus DB Bulk' }, + { key: '8', text: 'IPN Trigger', value: 'IPN Trigger' }, + { key: '9', text: 'Intranet Receipt', value: 'Intranet Receipt' }, + { key: '10', text: 'Automatic', value: 'Automatic' }, + { key: '11', text: 'Manual', value: 'Manual' }, + ]; + + const categoryOptions = [ + { key: '0', text: 'Membership Dues', value: 'Membership' }, + { key: '1', text: 'Payment On Account or Prepayment', value: 'OnAcct' }, + { key: '2', text: 'Snack / Pop / Coffee', value: 'Snacks' }, + { key: '3', text: 'Donations', value: 'Donation' }, + { key: '4', text: 'Consumables (Specify which in memo)', value: 'Consumables' }, + { key: '5', text: 'Purchases of Goods or Parts or Stock', value: 'Purchases' }, + { key: '6', text: 'Auction, Garage Sale, Nearly Free Shelf, Etc.', value: 'Garage Sale' }, + { key: '7', text: 'Reimbursement (Enter a negative value)', value: 'Reimburse' }, + { key: '8', text: 'Other (Explain in memo)', value: 'Other' }, + ]; + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +function EditTransaction(props) { + const { transaction, setTransaction, token, refreshUser } = props; + const [input, setInput] = useState(transaction); + 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); + requester('/transactions/'+id+'/', 'PUT', token, input) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + setInput(res); + setTransaction(res); + if (refreshUser) { + refreshUser(); + } + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + return ( +
+
Edit Transaction
+ +
+ + + {success &&

Success!

} + + Save + + +
+ ); +}; + export function Transactions(props) { const { user } = props; @@ -45,56 +218,93 @@ export function Transactions(props) { }; export function TransactionDetail(props) { - const { user } = props; + const { token, user } = props; const { id } = useParams(); + const ownTransaction = user.transactions.find(x => x.id == id); + const [transaction, setTransaction] = useState(ownTransaction || false); + const [error, setError] = useState(false); - const t = user.transactions.find(x => x.id == id); + useEffect(() => { + requester('/transactions/'+id+'/', 'GET', token) + .then(res => { + setTransaction(res); + setError(false); + }) + .catch(err => { + console.log(err); + setError(true); + }); + }, []); return ( - t ? - -
Transaction Receipt
+ + {!error ? + transaction ? +
+
Transaction Receipt
- - - - Date: - {t.date} - - - ID: - {t.id} - - - Amount: - ${t.amount} - - - Category: - {t.category} - - - Account: - {t.account_type} - - - Info Source: - {t.info_source} - - - Reference: - {t.reference_number} - - - Memo: - {t.memo} - - - + + + + + + Member: + {transaction.member_name} + + + ID: + {transaction.id} + + + Date: + {transaction.date} + + + Amount: + ${transaction.amount} + + + Category: + {transaction.category} + + + Account: + {transaction.account_type} + + + Payment Method + {transaction.payment_method} + + + Info Source: + {transaction.info_source} + + + Reference: + {transaction.reference_number} + + + Memo: + {transaction.memo} + + + + - - : - + + {isAdmin(user) && + + } + + + +
+ : +

Loading...

+ : + + } +
); }; diff --git a/webclient/src/light.css b/webclient/src/light.css index 4b9912d..918a27c 100644 --- a/webclient/src/light.css +++ b/webclient/src/light.css @@ -63,7 +63,7 @@ body { padding-bottom: 24rem; } -.course-editor, .class-editor { +.course-editor, .class-editor, .transaction-editor { margin-bottom: 1rem; }