Add UI page for reported transactions, member dropdown

This commit is contained in:
Tanner Collin 2020-01-31 23:47:52 +00:00
parent 7d3d06033d
commit 9fd7e76486
4 changed files with 121 additions and 7 deletions

View File

@ -6,6 +6,42 @@ import moment from 'moment';
import { statusColor, BasicTable, staticUrl, requester } from './utils.js'; import { statusColor, BasicTable, staticUrl, requester } from './utils.js';
import { TransactionList, TransactionEditor } from './Transactions.js'; import { TransactionList, TransactionEditor } from './Transactions.js';
export function AdminReportedTransactions(props) {
const { token, user } = props;
const [transactions, setTransactions] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
requester('/transactions/', 'GET', token)
.then(res => {
setTransactions(res.results);
setError(false);
})
.catch(err => {
console.log(err);
setError(true);
});
}, []);
return (
<Container>
<Header size='large'>Reported Transactions</Header>
{!error ?
transactions ?
<div>
<TransactionList transactions={transactions} />
</div>
:
<p>Loading...</p>
:
<NotFound />
}
</Container>
);
};
export function AdminTransactions(props) { export function AdminTransactions(props) {
const { token, result, refreshResult } = props; const { token, result, refreshResult } = props;
const transactions = result.transactions; const transactions = result.transactions;

View File

@ -3,12 +3,13 @@ import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } f
import './semantic-ui/semantic.min.css'; import './semantic-ui/semantic.min.css';
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 { requester } from './utils.js'; import { isAdmin, requester } from './utils.js';
import { Home } from './Home.js'; import { Home } from './Home.js';
import { Account } from './Account.js'; import { Account } from './Account.js';
import { Transactions, TransactionDetail } from './Transactions.js'; import { Transactions, TransactionDetail } from './Transactions.js';
import { Cards } from './Cards.js'; import { Cards } from './Cards.js';
import { Training } from './Training.js'; import { Training } from './Training.js';
import { AdminReportedTransactions } from './Admin.js';
import { Courses, CourseDetail } from './Courses.js'; import { Courses, CourseDetail } from './Courses.js';
import { Classes, ClassDetail } from './Classes.js'; import { Classes, ClassDetail } from './Classes.js';
import { Members, MemberDetail } from './Members.js'; import { Members, MemberDetail } from './Members.js';
@ -116,6 +117,12 @@ function App() {
as={Link} as={Link}
to='/classes' to='/classes'
/> />
{user && isAdmin(user) && <Dropdown.Item
content='Admin Trans.'
as={Link}
to='/admintrans'
/>}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
@ -177,6 +184,12 @@ function App() {
<Members token={token} /> <Members token={token} />
</Route> </Route>
{user && isAdmin(user) &&
<Route path='/admintrans'>
<AdminReportedTransactions token={token} user={user} />
</Route>
}
<Route path='/:page'> <Route path='/:page'>
<NotFound /> <NotFound />
</Route> </Route>

View File

@ -6,6 +6,50 @@ import { statusColor, isAdmin, BasicTable, staticUrl, requester } from './utils.
import { NotFound, PleaseLogin } from './Misc.js'; import { NotFound, PleaseLogin } from './Misc.js';
import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards, AdminTransactions } from './Admin.js'; import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards, AdminTransactions } from './Admin.js';
export function MembersDropdown(props) {
const { token, name, onChange, value, initial } = props;
const [response, setResponse] = useState({ results: [] });
const searchDefault = {seq: 0, q: initial || ''};
const [search, setSearch] = useState(searchDefault);
useEffect(() => {
requester('/search/', 'POST', token, search)
.then(res => {
if (!search.seq || res.seq > response.seq) {
setResponse(res);
}
})
.catch(err => {
console.log(err);
});
}, [search]);
const options = response.results.map((x, i) => ({
key: x.member.id,
value: x.member.id,
text: x.member.preferred_name + ' ' + x.member.last_name,
image: { avatar: true, src: x.member.photo_small ? staticUrl + '/' + x.member.photo_small : '/nophoto.png' },
}));
console.log(value, initial);
return (
<Dropdown
clearable
fluid
selection
search
name={name}
options={options}
value={value}
placeholder='Search for Member'
onChange={onChange}
onSearchChange={(e, v) => setSearch({seq: parseInt(e.timeStamp), q: v.searchQuery})}
/>
);
};
export function Members(props) { export function Members(props) {
const [response, setResponse] = useState(false); const [response, setResponse] = useState(false);
const searchDefault = {seq: 0, q: ''}; const searchDefault = {seq: 0, q: ''};

View File

@ -2,11 +2,12 @@ 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 { MembersDropdown } from './Members.js';
import { isAdmin, BasicTable, requester } from './utils.js'; import { isAdmin, BasicTable, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js'; import { NotFound, PleaseLogin } from './Misc.js';
export function TransactionEditor(props) { export function TransactionEditor(props) {
const { input, setInput, error } = props; const { token, input, setInput, error } = props;
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] });
@ -26,7 +27,7 @@ export function TransactionEditor(props) {
{ key: '2', text: 'Square (Credit)', value: 'Square Pmt' }, { key: '2', text: 'Square (Credit)', value: 'Square Pmt' },
{ key: '3', text: 'Dream Payments (Debit/Credit)', value: 'Dream Pmt' }, { key: '3', text: 'Dream Payments (Debit/Credit)', value: 'Dream Pmt' },
{ key: '4', text: 'Deposit to TD (Not Interac)', value: 'TD Chequing' }, { key: '4', text: 'Deposit to TD (Not Interac)', value: 'TD Chequing' },
{ key: '5', text: 'PayPal', value: 'Paypal' }, { key: '5', text: 'PayPal', value: 'PayPal' },
{ key: '6', text: 'Member Balance / Protocash', value: 'Member' }, { key: '6', text: 'Member Balance / Protocash', value: 'Member' },
{ key: '7', text: 'Supense (Clearing) Acct / Membership Adjustment', value: 'Clearing' }, { key: '7', text: 'Supense (Clearing) Acct / Membership Adjustment', value: 'Clearing' },
]; ];
@ -37,7 +38,7 @@ export function TransactionEditor(props) {
{ key: '2', text: 'System', value: 'System' }, { key: '2', text: 'System', value: 'System' },
{ key: '3', text: 'Receipt or Statement', value: 'Receipt or Stmt' }, { key: '3', text: 'Receipt or Statement', value: 'Receipt or Stmt' },
{ key: '4', text: 'Quicken Import', value: 'Quicken Import' }, { key: '4', text: 'Quicken Import', value: 'Quicken Import' },
{ key: '5', text: 'PayPal IPN', value: 'Paypal IPN' }, { key: '5', text: 'PayPal IPN', value: 'PayPal IPN' },
{ key: '6', text: 'Auto', value: 'Auto' }, { key: '6', text: 'Auto', value: 'Auto' },
{ key: '7', text: 'Nexus Database Bulk', value: 'Nexus DB Bulk' }, { key: '7', text: 'Nexus Database Bulk', value: 'Nexus DB Bulk' },
{ key: '8', text: 'IPN Trigger', value: 'IPN Trigger' }, { key: '8', text: 'IPN Trigger', value: 'IPN Trigger' },
@ -60,6 +61,16 @@ export function TransactionEditor(props) {
return ( return (
<div className='transaction-editor'> <div className='transaction-editor'>
<Form.Field error={error.member_id}>
<label>Member (search)</label>
<MembersDropdown
token={token}
{...makeProps('member_id')}
onChange={handleValues}
initial={input.member_name}
/>
</Form.Field>
<Form.Group widths='equal'> <Form.Group widths='equal'>
<Form.Input <Form.Input
label='Date' label='Date'
@ -144,7 +155,8 @@ function EditTransaction(props) {
const handleSubmit = (e) => { const handleSubmit = (e) => {
setLoading(true); setLoading(true);
setSuccess(false); setSuccess(false);
requester('/transactions/'+id+'/', 'PUT', token, input) const data = { ...input, report_type: null, report_memo: '' };
requester('/transactions/'+id+'/', 'PUT', token, data)
.then(res => { .then(res => {
setLoading(false); setLoading(false);
setSuccess(true); setSuccess(true);
@ -167,7 +179,7 @@ function EditTransaction(props) {
<Header size='medium'>Edit Transaction</Header> <Header size='medium'>Edit Transaction</Header>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<TransactionEditor input={input} setInput={setInput} error={error} /> <TransactionEditor token={token} input={input} setInput={setInput} error={error} />
<Form.Button loading={loading} error={error.non_field_errors}> <Form.Button loading={loading} error={error.non_field_errors}>
Save Save
@ -201,7 +213,7 @@ export function TransactionList(props) {
</Table.Cell> </Table.Cell>
<Table.Cell>${x.amount}</Table.Cell> <Table.Cell>${x.amount}</Table.Cell>
<Table.Cell>{x.account_type}</Table.Cell> <Table.Cell>{x.account_type}</Table.Cell>
<Table.Cell>{x.memo}</Table.Cell> <Table.Cell>{x.memo || x.report_memo}</Table.Cell>
</Table.Row> </Table.Row>
) )
: :
@ -295,6 +307,15 @@ export function TransactionDetail(props) {
<Table.Cell>Memo:</Table.Cell> <Table.Cell>Memo:</Table.Cell>
<Table.Cell>{transaction.memo}</Table.Cell> <Table.Cell>{transaction.memo}</Table.Cell>
</Table.Row> </Table.Row>
{transaction.report_type && <Table.Row>
<Table.Cell>Report Type:</Table.Cell>
<Table.Cell>{transaction.report_type}</Table.Cell>
</Table.Row>}
{transaction.report_memo && <Table.Row>
<Table.Cell>Report Memo:</Table.Cell>
<Table.Cell>{transaction.report_memo}</Table.Cell>
</Table.Row>}
</Table.Body> </Table.Body>
</BasicTable> </BasicTable>
</Grid.Column> </Grid.Column>