Add transaction editor to UI
This commit is contained in:
parent
08bd52704b
commit
6a7f409986
|
@ -82,7 +82,9 @@ class HTMLField(serializers.CharField):
|
||||||
|
|
||||||
class TransactionSerializer(serializers.ModelSerializer):
|
class TransactionSerializer(serializers.ModelSerializer):
|
||||||
account_type = serializers.ChoiceField(['Interac', 'TD Chequing', 'Paypal', 'Dream Pmt', 'PayPal', 'Square Pmt', 'Member', 'Clearing', 'Cash'])
|
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:
|
class Meta:
|
||||||
model = models.Transaction
|
model = models.Transaction
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -99,6 +101,13 @@ class TransactionSerializer(serializers.ModelSerializer):
|
||||||
validated_data['user'] = member.user
|
validated_data['user'] = member.user
|
||||||
return super().create(validated_data)
|
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
|
# member viewing other members
|
||||||
|
@ -309,6 +318,7 @@ class UserTrainingSerializer(serializers.ModelSerializer):
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
training = UserTrainingSerializer(many=True)
|
training = UserTrainingSerializer(many=True)
|
||||||
member = MemberSerializer()
|
member = MemberSerializer()
|
||||||
|
transactions = TransactionSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
|
@ -133,7 +133,7 @@ function App() {
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path='/transactions/:id'>
|
<Route path='/transactions/:id'>
|
||||||
<TransactionDetail user={user} />
|
<TransactionDetail token={token} user={user} refreshUser={refreshUser} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/transactions'>
|
<Route path='/transactions'>
|
||||||
<Transactions user={user} />
|
<Transactions user={user} />
|
||||||
|
|
|
@ -2,9 +2,182 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
|
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
|
||||||
import './light.css';
|
import './light.css';
|
||||||
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
|
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';
|
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 (
|
||||||
|
<div className='transaction-editor'>
|
||||||
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Input
|
||||||
|
label='Date'
|
||||||
|
fluid
|
||||||
|
{...makeProps('date')}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
label='Amount'
|
||||||
|
fluid
|
||||||
|
{...makeProps('amount')}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Select
|
||||||
|
label='Category'
|
||||||
|
fluid
|
||||||
|
options={categoryOptions}
|
||||||
|
{...makeProps('category')}
|
||||||
|
onChange={handleValues}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Select
|
||||||
|
label='Account'
|
||||||
|
fluid
|
||||||
|
options={accountOptions}
|
||||||
|
{...makeProps('account_type')}
|
||||||
|
onChange={handleValues}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Input
|
||||||
|
label='Payment Method'
|
||||||
|
fluid
|
||||||
|
{...makeProps('payment_method')}
|
||||||
|
/>
|
||||||
|
<Form.Select
|
||||||
|
label='Info Source'
|
||||||
|
fluid
|
||||||
|
options={sourceOptions}
|
||||||
|
{...makeProps('info_source')}
|
||||||
|
onChange={handleValues}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Input
|
||||||
|
label='Reference Number'
|
||||||
|
fluid
|
||||||
|
{...makeProps('reference_number')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
label='# Membership Months'
|
||||||
|
fluid
|
||||||
|
{...makeProps('number_of_membership_months')}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
label='Memo / Notes'
|
||||||
|
fluid
|
||||||
|
{...makeProps('memo')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Header size='medium'>Edit Transaction</Header>
|
||||||
|
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<TransactionEditor input={input} setInput={setInput} error={error} />
|
||||||
|
|
||||||
|
{success && <p>Success!</p>}
|
||||||
|
<Form.Button loading={loading} error={error.non_field_errors}>
|
||||||
|
Save
|
||||||
|
</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function Transactions(props) {
|
export function Transactions(props) {
|
||||||
const { user } = props;
|
const { user } = props;
|
||||||
|
|
||||||
|
@ -45,56 +218,93 @@ export function Transactions(props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TransactionDetail(props) {
|
export function TransactionDetail(props) {
|
||||||
const { user } = props;
|
const { token, user } = props;
|
||||||
const { id } = useParams();
|
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 (
|
return (
|
||||||
t ?
|
|
||||||
<Container>
|
<Container>
|
||||||
|
{!error ?
|
||||||
|
transaction ?
|
||||||
|
<div>
|
||||||
<Header size='large'>Transaction Receipt</Header>
|
<Header size='large'>Transaction Receipt</Header>
|
||||||
|
|
||||||
|
<Grid stackable columns={2}>
|
||||||
|
<Grid.Column>
|
||||||
<BasicTable>
|
<BasicTable>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Date:</Table.Cell>
|
<Table.Cell>Member:</Table.Cell>
|
||||||
<Table.Cell>{t.date}</Table.Cell>
|
<Table.Cell>{transaction.member_name}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>ID:</Table.Cell>
|
<Table.Cell>ID:</Table.Cell>
|
||||||
<Table.Cell>{t.id}</Table.Cell>
|
<Table.Cell>{transaction.id}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>Date:</Table.Cell>
|
||||||
|
<Table.Cell>{transaction.date}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Amount:</Table.Cell>
|
<Table.Cell>Amount:</Table.Cell>
|
||||||
<Table.Cell>${t.amount}</Table.Cell>
|
<Table.Cell>${transaction.amount}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Category:</Table.Cell>
|
<Table.Cell>Category:</Table.Cell>
|
||||||
<Table.Cell>{t.category}</Table.Cell>
|
<Table.Cell>{transaction.category}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Account:</Table.Cell>
|
<Table.Cell>Account:</Table.Cell>
|
||||||
<Table.Cell>{t.account_type}</Table.Cell>
|
<Table.Cell>{transaction.account_type}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>Payment Method</Table.Cell>
|
||||||
|
<Table.Cell>{transaction.payment_method}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Info Source:</Table.Cell>
|
<Table.Cell>Info Source:</Table.Cell>
|
||||||
<Table.Cell>{t.info_source}</Table.Cell>
|
<Table.Cell>{transaction.info_source}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Reference:</Table.Cell>
|
<Table.Cell>Reference:</Table.Cell>
|
||||||
<Table.Cell>{t.reference_number}</Table.Cell>
|
<Table.Cell>{transaction.reference_number}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>Memo:</Table.Cell>
|
<Table.Cell>Memo:</Table.Cell>
|
||||||
<Table.Cell>{t.memo}</Table.Cell>
|
<Table.Cell>{transaction.memo}</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
</Grid.Column>
|
||||||
|
|
||||||
</Container>
|
<Grid.Column>
|
||||||
|
{isAdmin(user) && <Segment padded>
|
||||||
|
<EditTransaction transaction={transaction} setTransaction={setTransaction} {...props} />
|
||||||
|
</Segment>}
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<p>Loading...</p>
|
||||||
:
|
:
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ body {
|
||||||
padding-bottom: 24rem;
|
padding-bottom: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-editor, .class-editor {
|
.course-editor, .class-editor, .transaction-editor {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user