spaceport/webclient/src/Storage.js

508 lines
12 KiB
JavaScript
Raw Normal View History

2023-05-30 19:35:07 +00:00
import React, { useState, useEffect } from 'react';
import { Link, useParams, useHistory } from 'react-router-dom';
import './light.css';
import { MembersDropdown } from './Members.js';
import { statusColor, isAdmin, BasicTable, requester, useIsMobile } from './utils.js';
import { Button, Checkbox, Container, Form, Grid, Header, Icon, Input, Message, Segment, Table } from 'semantic-ui-react';
2023-05-30 19:35:07 +00:00
export function StorageEditor(props) {
const { token, input, setInput, error } = props;
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
const handleChange = (e) => handleValues(e, e.currentTarget);
const makeProps = (name) => ({
name: name,
onChange: handleChange,
value: input[name] || '',
error: error[name],
});
return (
<div className='transaction-editor'>
<Form.Field error={error.member_id}>
<label>Owner (search)</label>
<MembersDropdown
token={token}
{...makeProps('member_id')}
onChange={handleValues}
initial={input.member_name}
2023-05-30 20:36:08 +00:00
autofocus={!input.member_name}
2023-05-30 19:35:07 +00:00
/>
</Form.Field>
<Form.Input
label='Memo'
fluid
{...makeProps('memo')}
/>
</div>
);
};
function EditStorage(props) {
const { storage, setStorage, token, refreshUser } = props;
const [input, setInput] = useState(storage);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const { id } = useParams();
const handleSubmit = (e) => {
if (loading) return;
setLoading(true);
setSuccess(false);
const data = { ...input };
return requester('/storage/'+id+'/', 'PUT', token, data)
.then(res => {
setLoading(false);
setSuccess(true);
setError(false);
setInput(res);
setStorage(res);
})
.catch(err => {
setLoading(false);
console.log(err);
setError(err.data);
});
};
return (
<div>
<Header size='medium'>Edit Storage {storage.shelf_id}</Header>
2023-05-30 19:35:07 +00:00
<Form onSubmit={handleSubmit}>
<StorageEditor token={token} input={input} setInput={setInput} error={error} />
<Form.Group widths='equal'>
<Form.Button loading={loading} error={error.non_field_errors}>
Save
</Form.Button>
</Form.Group>
{success && <div>Success!</div>}
</Form>
</div>
);
};
function StorageTable(props) {
const { storage, user } = props;
2023-05-30 23:52:30 +00:00
const locations = {
member_shelves: 'Member Shelves',
lockers: 'Lockers',
large_project_storage: 'Large Project Storage',
};
2023-05-30 19:35:07 +00:00
return (
<BasicTable>
<Table.Body>
<Table.Row>
<Table.Cell>Shelf ID:</Table.Cell>
<Table.Cell>{storage.shelf_id}</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Owner:</Table.Cell>
2023-05-30 23:52:30 +00:00
{storage.member_id ?
2023-05-30 19:35:07 +00:00
<Table.Cell>
<Link to={'/members/'+storage.member_id}>
{storage.member_name}
</Link>
</Table.Cell>
:
2023-05-30 23:52:30 +00:00
<Table.Cell>None</Table.Cell>
2023-05-30 19:35:07 +00:00
}
</Table.Row>
<Table.Row>
<Table.Cell>Location:</Table.Cell>
2023-05-30 23:52:30 +00:00
<Table.Cell>
{locations[storage.location]}
<p>
Aisle {storage.shelf_id[0]} <br/>
Column {storage.shelf_id[1]} <br/>
Row {storage.shelf_id[2]}
</p>
</Table.Cell>
2023-05-30 19:35:07 +00:00
</Table.Row>
<Table.Row>
<Table.Cell>Memo:</Table.Cell>
<Table.Cell>{storage.memo || 'None'}</Table.Cell>
2023-05-30 19:35:07 +00:00
</Table.Row>
</Table.Body>
</BasicTable>
);
}
export function StorageDetail(props) {
const { token, user } = props;
const [storage, setStorage] = useState(false);
const [error, setError] = useState(false);
const { id } = useParams();
useEffect(() => {
requester('/storage/' + id + '/', 'GET', token)
.then(res => {
setStorage(res);
setError(false);
})
.catch(err => {
console.log(err);
setError(true);
});
}, [id]);
return (
<Container>
{!error ?
storage ?
<div>
<Header size='large'>Storage Location</Header>
2023-06-03 18:56:01 +00:00
<p><Link to={'/storage'}>View the list of all storage locations.</Link></p>
2023-05-30 19:35:07 +00:00
<Grid stackable columns={2}>
<Grid.Column width={6}>
<StorageTable user={user} storage={storage} />
</Grid.Column>
<Grid.Column width={10}>
{isAdmin(user) ?
<Segment padded>
<EditStorage storage={storage} setStorage={setStorage} token={token} {...props} />
</Segment>
:
<Segment padded>
<Header size='medium'>Report Storage</Header>
<p>If there's anything wrong with this storage location please email the Protospace Directors:</p>
<p><a href='mailto:directors@protospace.ca' target='_blank' rel='noopener noreferrer'>directors@protospace.ca</a></p>
<p>Please include a link to this storage location and any relevant details.</p>
</Segment>
}
</Grid.Column>
</Grid>
</div>
:
<p>Loading...</p>
:
<p>Error loading.</p>
}
</Container>
);
};
2023-06-03 18:56:01 +00:00
export function StorageButton(props) {
const { storage } = props;
const history = useHistory();
const buttonColors = {
member_shelves: 'grey',
lockers: 'blue',
large_project_storage: 'brown',
};
const handleStorageButton = (e, id) => {
e.preventDefault();
history.push('/storage/' + id);
};
return (
<Button
className='storage-button'
onClick={(e) => handleStorageButton(e, storage.id)}
size='tiny'
color={buttonColors[storage.location]}
>
{storage.shelf_id}
</Button>
);
};
2023-06-23 00:00:30 +00:00
let storageSearchCache = '';
export function StorageSearch(props) {
const { setSearch } = props;
const [input, setInput] = useState(storageSearchCache);
const handleChange = (event) => {
const q = event.target.value.toUpperCase();
setInput(q);
setSearch(q);
storageSearchCache = q;
};
return (
<div>
<Input icon='search'
placeholder='Search...'
value={input}
onChange={handleChange}
aria-label='search products'
style={{ marginRight: '0.5rem' }}
maxLength={3}
/>
{input.length ?
<Button
content='Clear'
onClick={() => {
setInput('');
setSearch('');
storageSearchCache = '';
}}
/> : ''
}
</div>
);
};
let storageListCache = false;
let showEmptyCache = false;
let showMemodCache = false;
let showServedCache = false;
let showExpiredCache = false;
2023-06-23 00:00:30 +00:00
2023-05-30 19:35:07 +00:00
export function StorageList(props) {
const { token } = props;
2023-06-23 00:00:30 +00:00
const [storageList, setStorageList] = useState(storageListCache);
const [search, setSearch] = useState(storageSearchCache);
const [showEmpty, setShowEmpty] = useState(showEmptyCache);
const [showMemod, setShowMemod] = useState(showMemodCache);
const [showServed, setShowServed] = useState(showServedCache);
const [showExpired, setShowExpired] = useState(showExpiredCache);
2023-05-30 19:35:07 +00:00
const [error, setError] = useState(false);
2023-06-03 18:56:01 +00:00
const isMobile = useIsMobile();
2023-05-30 19:35:07 +00:00
useEffect(() => {
requester('/storage/', 'GET', token)
.then(res => {
setStorageList(res.results);
2023-06-23 00:00:30 +00:00
storageListCache = res.results;
2023-05-30 19:35:07 +00:00
setError(false);
})
.catch(err => {
console.log(err);
setError(true);
});
}, []);
2023-06-23 00:00:30 +00:00
const filterStorage = (x) => {
if (search.length && !x.shelf_id.startsWith(search)) {
return false;
} else if (showEmpty && x.member_name) {
return false;
} else if (showMemod && !x.memo) {
return false;
} else if (showServed && !x.memo.toLowerCase().includes('served')) {
return false;
} else if (showExpired && !x.member_paused) {
return false;
2023-06-23 00:00:30 +00:00
} else {
return true;
}
};
const sortStorage = (a, b) => {
if (showExpired) {
return a.member_paused !== b.member_paused ? a.member_paused < b.member_paused ? -1 : 1 : 0;
} else {
return a.shelf_id !== b.shelf_id ? a.shelf_id < b.shelf_id ? -1 : 1 : 0;
}
};
const handleShowEmpty = (e, v) => {
setShowEmpty(v.checked);
showEmptyCache = v.checked;
};
const handleShowMemod = (e, v) => {
setShowMemod(v.checked);
showMemodCache = v.checked;
};
const handleShowServed = (e, v) => {
setShowServed(v.checked);
showServedCache = v.checked;
};
const handleShowExpired = (e, v) => {
setShowExpired(v.checked);
showExpiredCache = v.checked;
};
const numResults = storageList ? storageList.filter(filterStorage).length : 0;
2023-05-30 19:35:07 +00:00
return (
<div>
2023-06-03 18:56:01 +00:00
<p>
<Icon name='circle' color='grey' /> Member shelf <br/>
<Icon name='circle' color='blue' /> Locker <br/>
<Icon name='circle' color='brown' /> Large project storage
</p>
2023-06-23 00:00:30 +00:00
<p>
<StorageSearch setSearch={setSearch} />
</p>
<p>
<Checkbox
className='filter-option'
label='Show Empty'
onChange={handleShowEmpty}
checked={showEmpty}
/>
<Checkbox
className='filter-option'
label={'Show Memo\'d'}
onChange={handleShowMemod}
checked={showMemod}
/>
<Checkbox
className='filter-option'
label='Show Served'
onChange={handleShowServed}
checked={showServed}
/>
<Checkbox
className='filter-option'
label='Show Expired'
onChange={handleShowExpired}
checked={showExpired}
/>
</p>
2023-06-23 00:00:30 +00:00
2023-05-30 19:35:07 +00:00
{!error ?
storageList ?
<>
<p>{numResults} result{numResults === 1 ? '' : 's'}{showExpired && ', ordered by expiry'}:</p>
<Table basic='very'>
{!isMobile && <Table.Header>
<Table.Row>
<Table.HeaderCell>Shelf ID</Table.HeaderCell>
<Table.HeaderCell>Owner</Table.HeaderCell>
<Table.HeaderCell>Expired</Table.HeaderCell>
<Table.HeaderCell>Memo</Table.HeaderCell>
2023-06-03 18:56:01 +00:00
</Table.Row>
</Table.Header>}
<Table.Body>
{storageList.filter(filterStorage).sort(sortStorage).map(x =>
<Table.Row key={x.id}>
<Table.Cell><StorageButton storage={x} /></Table.Cell>
<Table.Cell>
{isMobile && 'Owner: '}
{x.member_name && <Icon name='circle' color={statusColor[x.member_status]} />}
<Link to={'/members/'+x.member_id}>{x.member_name}</Link>
</Table.Cell>
<Table.Cell>{isMobile && 'Expired: '}{x.member_paused}</Table.Cell>
<Table.Cell>{isMobile && 'Memo: '}{x.memo}</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
</>
2023-05-30 19:35:07 +00:00
:
<p>Loading...</p>
:
<p>Error loading.</p>
}
</div>
);
};
2023-05-30 23:44:05 +00:00
2023-06-03 18:56:01 +00:00
export function Storage(props) {
const { token, user } = props;
return (
<Container>
<Header size='large'>Storage Locations</Header>
<StorageList {...props} />
</Container>
);
};
2023-05-30 23:44:05 +00:00
export function ClaimShelfForm(props) {
const { token, user, refreshUser } = props;
const member = user.member;
const [input, setInput] = useState({});
const [error, setError] = useState({});
const [loading, setLoading] = useState(false);
const history = useHistory();
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
const handleChange = (e) => handleValues(e, e.currentTarget);
const handleSubmit = (e) => {
if (loading) return;
setLoading(true);
requester('/storage/claim/', 'POST', token, input)
.then(res => {
setError({});
refreshUser();
history.push('/');
})
.catch(err => {
setLoading(false);
console.log(err);
setError(err.data);
});
};
const makeProps = (name) => ({
name: name,
onChange: handleChange,
value: input[name] || '',
error: error[name],
});
return (
<Form onSubmit={handleSubmit}>
<div className='field'>
<label>Spaceport Username</label>
<p>{user.username}</p>
</div>
<Form.Input
label='Shelf ID'
autoComplete='off'
required
{...makeProps('shelf_id')}
maxLength={3}
/>
<Form.Button loading={loading} error={error.non_field_errors || error.detail}>
Submit
</Form.Button>
</Form>
);
};
export function ClaimShelf(props) {
const { token, user } = props;
return (
<Container>
<Grid stackable columns={2}>
<Grid.Column>
<Header size='large'>Claim Member Shelf</Header>
<p>Use this form to claim a member shelf.</p>
<p>Please make sure your name and contact info are visible on the shelf.</p>
<p>Use the Shelf ID visible on the corner label (A1A, A2B, etc.)</p>
<ClaimShelfForm {...props} />
</Grid.Column>
</Grid>
</Container>
);
};