Skip to content

Commit

Permalink
OpenConceptLab/ocl_issues#1059 | Favorite/Pinned items header panel
Browse files Browse the repository at this point in the history
  • Loading branch information
snyaggarwal committed Nov 2, 2021
1 parent 773fa21 commit c2132ee
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 48 deletions.
161 changes: 161 additions & 0 deletions src/components/app/Favorites.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React from 'react';
import { isEmpty, groupBy, map, orderBy, reject } from 'lodash';
import {
Paper, IconButton, Popper, Grow, ClickAwayListener, Tooltip,
List, ListItem, ListItemIcon, ListItemText,
ListSubheader, CircularProgress, ListItemButton
} from '@mui/material';
import { Refresh as RefreshIcon } from '@mui/icons-material';
import APIService from '../../services/APIService';
import { getCurrentUserUsername, defaultDeletePin, } from '../../common/utils'
import PinIcon from '../common/PinIcon';
import DynamicConfigResourceIcon from '../common/DynamicConfigResourceIcon';

const Favorites = () => {
const username = getCurrentUserUsername()
const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const [objects, setObjects] = React.useState({});
const anchorRef = React.useRef(null);
const handleToggle = () => setOpen(prevOpen => {
const newOpen = !prevOpen
if(newOpen)
fetchFavorites()
return newOpen
});

const handleClose = event => {
if (anchorRef.current && anchorRef.current.contains(event.target))
return;

setOpen(false);
};

const fetchFavorites = reload => {
if(username && (isEmpty(objects) || reload)) {
setLoading(true)
__fetchFavorites()
}
}

const __fetchFavorites = () => {
getService()
.get()
.then(
response => {
setObjects(response.data)
setLoading(false)
})
}

const removePin = pinId => defaultDeletePin(getService(pinId), setObjects(reject(objects, {id: pinId})))

const getService = pinId => APIService.users(username).pins(pinId)

const getLabel = object => {
if(object.owner)
return `${object.owner}/${object.short_code}`
return `${object.name}`
}

const groupedObjects = groupBy(objects, 'resource.type')

return (
<React.Fragment>
<Tooltip arrow title='Favorites'>
<IconButton
ref={anchorRef}
aria-controls={open ? 'favorite-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
touch='true'
size="large"
color={open ? 'primary' : 'default'}
>
<PinIcon pinned="true" />
</IconButton>
</Tooltip>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal style={{zIndex: 1}}>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper style={{minWidth: '330px', border: '1px solid lightgray'}}>
<ClickAwayListener onClickAway={handleClose}>
<div>
{
loading ?
<div className='flex-vertical-center' style={{height: '200px', width: '100%', justifyContent: 'center'}}>
<CircularProgress />
</div> :
<React.Fragment>
<div style={{width: '100%', padding: '5px 10px', justifyContent: 'space-between', background: 'rgba(0, 0, 0, 0.1)', marginBottom: '5px'}} className='flex-vertical-center'>
<span style={{textAlign: 'left'}}>
<b>Favorites</b>
</span>
<span style={{textAlign: 'right'}}>
<IconButton color='primary' size='small' onClick={() => fetchFavorites(true)}>
<RefreshIcon fontSize='inherit' />
</IconButton>
</span>
</div>
{
isEmpty(groupedObjects) &&
<List dense style={{textAlign: 'left'}} subheader={
<ListSubheader style={{lineHeight: '24px', padding: '0 10px', fontSize: '0.8rem'}} component="div" id="nested-list-subheader">
Pin resources <a href={`#/users/${username}/`}>here</a>
</ListSubheader>
}/>
}
{
map(groupedObjects, (resources, type) => {
return (
<List dense style={{textAlign: 'left'}} key={type} subheader={
<ListSubheader style={{lineHeight: '24px', padding: '0 10px', fontSize: '0.8rem'}} component="div" id="nested-list-subheader">
{`${type}s`}
</ListSubheader>
}>
{
map(orderBy(resources, ['created_at', 'resource.name'], ['desc', 'asc']), resource => (
<ListItem disablePadding key={resource.id} secondaryAction={
<Tooltip title='Remove'>
<IconButton edge="end" aria-label="pin" size='small' onClick={() => removePin(resource.id)}>
<PinIcon pinned="true" fontSize='inherit' />
</IconButton>
</Tooltip>
}>
<ListItemButton role={undefined} href={`#${resource.resource_uri}`} dense component="a" style={{padding: '0 15px'}}>
<ListItemIcon style={{minWidth: 'auto', marginRight: '5px'}}>
<DynamicConfigResourceIcon
resource={type.toLowerCase()}
fontSize='inherit'
enableColor
/>
</ListItemIcon>
<ListItemText primary={getLabel(resource.resource)} />
</ListItemButton>
</ListItem>
))
}
</List>
)
})
}
</React.Fragment>
}
</div>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</React.Fragment>
)
}

export default Favorites;
2 changes: 2 additions & 0 deletions src/components/app/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { WHITE, BLACK } from '../../common/constants';
import SearchInput from '../search/SearchInput';
import UserOptions from '../users/UserOptions';
import Favorites from './Favorites';
import { OPTIONS, SITE_URL } from './MenuOptions.jsx';
/* import Feedback from '../common/Feedback'; */
import AppsMenu from '../common/AppsMenu';
Expand Down Expand Up @@ -166,6 +167,7 @@ const Header = props => {
{
authenticated ?
<span style={{marginLeft: '20px'}}>
<Favorites />
<AppsMenu/>
<UserOptions />
</span> :
Expand Down
66 changes: 34 additions & 32 deletions src/components/common/AppsMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const AppsMenu = () => {
touch='true'
onClick={open.toggle}
ref={anchorRef}
size="large">
size="large"
color={open.value ? 'primary' : 'default'}
>
<AppsIcon/>
</IconButton>
</Tooltip>
Expand All @@ -38,37 +40,37 @@ const AppsMenu = () => {
style={{
transformOrigin:'right top',
}}
>
<Paper className="app-menu flex-vertical-center">
<ClickAwayListener onClickAway={handleClose}>
<Box className="app-container" display="flex" justifyContent="space-around">
<Link to="/" className='no-anchor-styles flex-vertical-center'>
<Box className={location.pathname !== "/imports" ? "app selected" : "app"} display="flex" flexDirection="column" alignItems="center">
<MetadataBrowserIcon fontSize="large"/>
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
OCL <br/> TermBrowser
</Typography>
</Box>
</Link>
<a href={getOpenMRSURL()} className='no-anchor-styles flex-vertical-center'>
<Box className="app" display="flex" flexDirection="column" alignItems="center">
<OpenMRSLogo style={{width:"30px"}} />
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
OpenMRS <br/> Dictionary <br/> Manager
</Typography>
</Box>
</a>
<Link to='/imports' className='no-anchor-styles flex-vertical-center' onClick={handleClose}>
<Box className={location.pathname == "/imports" ? "app selected" : "app"} display="flex" flexDirection="column" alignItems="center">
<ImportsIcon fontSize="large"/>
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
Bulk <br/> Importer
</Typography>
</Box>
</Link>
</Box>
</ClickAwayListener>
</Paper>
>
<Paper className="app-menu flex-vertical-center">
<ClickAwayListener onClickAway={handleClose}>
<Box className="app-container" display="flex" justifyContent="space-around">
<Link to="/" className='no-anchor-styles flex-vertical-center'>
<Box className={location.pathname !== "/imports" ? "app selected" : "app"} display="flex" flexDirection="column" alignItems="center">
<MetadataBrowserIcon fontSize="large"/>
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
OCL <br/> TermBrowser
</Typography>
</Box>
</Link>
<a href={getOpenMRSURL()} className='no-anchor-styles flex-vertical-center'>
<Box className="app" display="flex" flexDirection="column" alignItems="center">
<OpenMRSLogo style={{width:"30px"}} />
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
OpenMRS <br/> Dictionary <br/> Manager
</Typography>
</Box>
</a>
<Link to='/imports' className='no-anchor-styles flex-vertical-center' onClick={handleClose}>
<Box className={location.pathname == "/imports" ? "app selected" : "app"} display="flex" flexDirection="column" alignItems="center">
<ImportsIcon fontSize="large"/>
<Typography style={{lineHeight:"1.2", marginTop:"15px"}} align="center" component="h6">
Bulk <br/> Importer
</Typography>
</Box>
</Link>
</Box>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
Expand Down
36 changes: 20 additions & 16 deletions src/components/common/DynamicConfigResourceIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,30 @@ import {
AccountTreeRounded as VersionIcon, LocalOffer as ConceptIcon
} from '@mui/icons-material';
import { includes, snakeCase } from 'lodash';
import { GREEN, BLUE, ORANGE } from '../../common/constants';

const DynamicConfigResourceIcon = ({resource, index, style, icon}) => {
const DynamicConfigResourceIcon = ({resource, index, style, icon, enableColor, ...rest}) => {
const styles = style || {}
if(icon)
return (<Icon style={{fontSize: '20px'}}>{snakeCase(icon)}</Icon>)
if(resource === 'sources')
return (<SourceIcon style={style} />)
if(resource === 'collections')
return <CollectionIcon style={style} />
if(resource === 'users')
return <UserIcon style={style} />
if(resource === 'concepts')
return <ConceptIcon style={style} />
if(resource === 'mappings')
return <MappingIcon style={style} />
if(includes(['versions', 'history'], resource))
return <VersionIcon style={style} />
return (<Icon style={{fontSize: '20px'}} {...rest}>{snakeCase(icon)}</Icon>)
if(includes(['source', 'sources'], resource))
return <SourceIcon style={{...styles, color: enableColor ? GREEN : ''}} {...rest} />;
if(includes(['collection', 'collections'], resource))
return <CollectionIcon style={{...styles, color: enableColor ? GREEN : ''}} {...rest} />;
if(includes(['user', 'users'], resource))
return <UserIcon style={{...styles, color: enableColor ? ORANGE : ''}} {...rest} />;
if(includes(['org', 'orgs', 'organizations', 'organization'], resource))
return <HomeIcon style={{...styles, color: enableColor ? ORANGE : ''}} {...rest} />;
if(includes(['concept', 'concepts'], resource))
return <ConceptIcon style={{...styles, color: enableColor ? BLUE : ''}} {...rest} />;
if(includes(['mapping', 'mappings'], resource))
return <MappingIcon style={{...styles, color: enableColor ? BLUE : ''}} {...rest} />;
if(includes(['versions', 'history', 'version'], resource))
return <VersionIcon style={styles} {...rest} />;
if(index === 0)
return <HomeIcon style={style} />
return <HomeIcon style={styles} />;
if(includes(['about', 'text'], resource))
return <InfoIcon style={style} />
return <InfoIcon style={styles} {...rest} />;

return '';
}
Expand Down

0 comments on commit c2132ee

Please sign in to comment.