mirror of
https://github.com/pawelmalak/flame.git
synced 2025-08-01 17:05:18 +02:00
Apps reordering. Sorting apps while adding them
This commit is contained in:
parent
9a1ec76ffd
commit
ce173f2c42
12 changed files with 219 additions and 53 deletions
|
@ -1,7 +1,8 @@
|
|||
import { KeyboardEvent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { App, GlobalState } from '../../../interfaces';
|
||||
import { pinApp, deleteApp } from '../../../store/actions';
|
||||
import { pinApp, deleteApp, reorderApp } from '../../../store/actions';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
||||
|
||||
import classes from './AppTable.module.css';
|
||||
import Icon from '../../UI/Icons/Icon/Icon';
|
||||
|
@ -12,6 +13,7 @@ interface ComponentProps {
|
|||
pinApp: (app: App) => void;
|
||||
deleteApp: (id: number) => void;
|
||||
updateAppHandler: (app: App) => void;
|
||||
reorderApp: (apps: App[]) => void;
|
||||
}
|
||||
|
||||
const AppTable = (props: ComponentProps): JSX.Element => {
|
||||
|
@ -29,49 +31,89 @@ const AppTable = (props: ComponentProps): JSX.Element => {
|
|||
}
|
||||
}
|
||||
|
||||
const dragEndHanlder = (result: DropResult): void => {
|
||||
console.log(result);
|
||||
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmpApps = [...props.apps];
|
||||
const [movedApp] = tmpApps.splice(result.source.index, 1);
|
||||
tmpApps.splice(result.destination.index, 0, movedApp);
|
||||
|
||||
props.reorderApp(tmpApps);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table headers={[
|
||||
'Name',
|
||||
'URL',
|
||||
'Icon',
|
||||
'Actions'
|
||||
]}>
|
||||
{props.apps.map((app: App): JSX.Element => {
|
||||
return (
|
||||
<tr key={app.id}>
|
||||
<td>{app.name}</td>
|
||||
<td>{app.url}</td>
|
||||
<td>{app.icon}</td>
|
||||
<td className={classes.TableActions}>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => deleteAppHandler(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
|
||||
tabIndex={0}>
|
||||
<Icon icon='mdiDelete' />
|
||||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.updateAppHandler(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
|
||||
tabIndex={0}>
|
||||
<Icon icon='mdiPencil' />
|
||||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.pinApp(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
|
||||
tabIndex={0}>
|
||||
{app.isPinned
|
||||
? <Icon icon='mdiPinOff' color='var(--color-accent)' />
|
||||
: <Icon icon='mdiPin' />
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</Table>
|
||||
<DragDropContext onDragEnd={dragEndHanlder}>
|
||||
<Droppable droppableId='apps'>
|
||||
{(provided) => (
|
||||
<Table headers={[
|
||||
'Name',
|
||||
'URL',
|
||||
'Icon',
|
||||
'Actions'
|
||||
]}
|
||||
innerRef={provided.innerRef}>
|
||||
{props.apps.map((app: App, index): JSX.Element => {
|
||||
return (
|
||||
<Draggable key={app.id} draggableId={app.id.toString()} index={index}>
|
||||
{(provided, snapshot) => {
|
||||
const style = {
|
||||
border: snapshot.isDragging ? '1px solid var(--color-accent)' : 'none',
|
||||
borderRadius: '4px',
|
||||
...provided.draggableProps.style,
|
||||
};
|
||||
|
||||
return (
|
||||
<tr
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
style={style}
|
||||
>
|
||||
<td style={{width:'200px'}}>{app.name}</td>
|
||||
<td style={{width:'200px'}}>{app.url}</td>
|
||||
<td style={{width:'200px'}}>{app.icon}</td>
|
||||
{!snapshot.isDragging && (
|
||||
<td className={classes.TableActions}>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => deleteAppHandler(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
|
||||
tabIndex={0}>
|
||||
<Icon icon='mdiDelete' />
|
||||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.updateAppHandler(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
|
||||
tabIndex={0}>
|
||||
<Icon icon='mdiPencil' />
|
||||
</div>
|
||||
<div
|
||||
className={classes.TableAction}
|
||||
onClick={() => props.pinApp(app)}
|
||||
onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
|
||||
tabIndex={0}>
|
||||
{app.isPinned
|
||||
? <Icon icon='mdiPinOff' color='var(--color-accent)' />
|
||||
: <Icon icon='mdiPin' />
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</Draggable>
|
||||
)
|
||||
})}
|
||||
</Table>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -81,4 +123,4 @@ const mapStateToProps = (state: GlobalState) => {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { pinApp, deleteApp })(AppTable);
|
||||
export default connect(mapStateToProps, { pinApp, deleteApp, reorderApp })(AppTable);
|
|
@ -8,15 +8,17 @@
|
|||
text-align: left;
|
||||
font-size: 16px;
|
||||
color: var(--color-primary);
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.Table th,
|
||||
.Table td {
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Head */
|
||||
|
||||
.Table th {
|
||||
--header-radius: 4px;
|
||||
background-color: var(--color-primary);
|
||||
|
@ -34,8 +36,6 @@
|
|||
}
|
||||
|
||||
/* Body */
|
||||
|
||||
.Table td {
|
||||
/* opacity: 0.5; */
|
||||
transition: all 0.2s;
|
||||
}
|
|
@ -3,11 +3,12 @@ import classes from './Table.module.css';
|
|||
interface ComponentProps {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
headers: string[];
|
||||
innerRef?: any;
|
||||
}
|
||||
|
||||
const Table = (props: ComponentProps): JSX.Element => {
|
||||
return (
|
||||
<div className={classes.TableContainer}>
|
||||
<div className={classes.TableContainer} ref={props.innerRef}>
|
||||
<table className={classes.Table}>
|
||||
<thead className={classes.TableHead}>
|
||||
<tr>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
AddAppAction,
|
||||
DeleteAppAction,
|
||||
UpdateAppAction,
|
||||
ReorderAppAction,
|
||||
// Categories
|
||||
GetCategoriesAction,
|
||||
AddCategoryAction,
|
||||
|
@ -37,6 +38,7 @@ export enum ActionTypes {
|
|||
addAppSuccess = 'ADD_APP_SUCCESS',
|
||||
deleteApp = 'DELETE_APP',
|
||||
updateApp = 'UPDATE_APP',
|
||||
reorderApp = 'REORDER_APP',
|
||||
// Categories
|
||||
getCategories = 'GET_CATEGORIES',
|
||||
getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS',
|
||||
|
@ -66,6 +68,7 @@ export type Action =
|
|||
AddAppAction |
|
||||
DeleteAppAction |
|
||||
UpdateAppAction |
|
||||
ReorderAppAction |
|
||||
// Categories
|
||||
GetCategoriesAction<any> |
|
||||
AddCategoryAction |
|
||||
|
|
|
@ -132,4 +132,36 @@ export const updateApp = (id: number, formData: NewApp) => async (dispatch: Disp
|
|||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReorderAppAction {
|
||||
type: ActionTypes.reorderApp;
|
||||
payload: App[]
|
||||
}
|
||||
|
||||
interface ReorderQuery {
|
||||
apps: {
|
||||
id: number;
|
||||
orderId: number;
|
||||
}[]
|
||||
}
|
||||
|
||||
export const reorderApp = (apps: App[]) => async (dispatch: Dispatch) => {
|
||||
try {
|
||||
const updateQuery: ReorderQuery = { apps: [] }
|
||||
|
||||
apps.forEach((app, index) => updateQuery.apps.push({
|
||||
id: app.id,
|
||||
orderId: index + 1
|
||||
}))
|
||||
|
||||
await axios.put<{}>('/api/apps/0/reorder', updateQuery);
|
||||
|
||||
dispatch<ReorderAppAction>({
|
||||
type: ActionTypes.reorderApp,
|
||||
payload: apps
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
|
@ -52,8 +52,12 @@ const pinApp = (state: State, action: Action): State => {
|
|||
}
|
||||
|
||||
const addAppSuccess = (state: State, action: Action): State => {
|
||||
const tmpApps = [...state.apps, action.payload];
|
||||
|
||||
const tmpApps: App[] = [...state.apps, action.payload].sort((a: App, b: App) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) { return -1 }
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) { return 1 }
|
||||
return 0;
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
apps: tmpApps
|
||||
|
@ -85,6 +89,13 @@ const updateApp = (state: State, action: Action): State => {
|
|||
}
|
||||
}
|
||||
|
||||
const reorderApp = (state: State, action: Action): State => {
|
||||
return {
|
||||
...state,
|
||||
apps: action.payload
|
||||
}
|
||||
}
|
||||
|
||||
const appReducer = (state = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getApps: return getApps(state, action);
|
||||
|
@ -94,6 +105,7 @@ const appReducer = (state = initialState, action: Action) => {
|
|||
case ActionTypes.addAppSuccess: return addAppSuccess(state, action);
|
||||
case ActionTypes.deleteApp: return deleteApp(state, action);
|
||||
case ActionTypes.updateApp: return updateApp(state, action);
|
||||
case ActionTypes.reorderApp: return reorderApp(state, action);
|
||||
default: return state;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue