Sorting settings. Sort apps on change/add/update

This commit is contained in:
unknown 2021-06-18 10:38:05 +02:00
parent 8974fb3b49
commit 754dc3a7b9
9 changed files with 124 additions and 27 deletions

View file

@ -1 +1 @@
REACT_APP_VERSION=1.3.3 REACT_APP_VERSION=1.3.5

View file

@ -2,7 +2,7 @@ import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
// Redux // Redux
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createNotification, updateConfig } from '../../../store/actions'; import { createNotification, updateConfig, sortApps } from '../../../store/actions';
// Typescript // Typescript
import { GlobalState, NewNotification, SettingsForm } from '../../../interfaces'; import { GlobalState, NewNotification, SettingsForm } from '../../../interfaces';
@ -17,6 +17,7 @@ import { searchConfig } from '../../../utility';
interface ComponentProps { interface ComponentProps {
createNotification: (notification: NewNotification) => void; createNotification: (notification: NewNotification) => void;
updateConfig: (formData: SettingsForm) => void; updateConfig: (formData: SettingsForm) => void;
sortApps: () => void;
loading: boolean; loading: boolean;
} }
@ -26,7 +27,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
customTitle: document.title, customTitle: document.title,
pinAppsByDefault: 1, pinAppsByDefault: 1,
pinCategoriesByDefault: 1, pinCategoriesByDefault: 1,
hideHeader: 0 hideHeader: 0,
useOrdering: 'createdAt'
}) })
// Get config // Get config
@ -35,7 +37,8 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
customTitle: searchConfig('customTitle', 'Flame'), customTitle: searchConfig('customTitle', 'Flame'),
pinAppsByDefault: searchConfig('pinAppsByDefault', 1), pinAppsByDefault: searchConfig('pinAppsByDefault', 1),
pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1), pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1),
hideHeader: searchConfig('hideHeader', 0) hideHeader: searchConfig('hideHeader', 0),
useOrdering: searchConfig('useOrdering', 'createdAt')
}) })
}, [props.loading]); }, [props.loading]);
@ -46,8 +49,11 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
// Save settings // Save settings
await props.updateConfig(formData); await props.updateConfig(formData);
// update local page title // Update local page title
document.title = formData.customTitle; document.title = formData.customTitle;
// Get sorted apps
props.sortApps();
} }
// Input handler // Input handler
@ -113,6 +119,19 @@ const OtherSettings = (props: ComponentProps): JSX.Element => {
<option value={0}>False</option> <option value={0}>False</option>
</select> </select>
</InputGroup> </InputGroup>
<InputGroup>
<label htmlFor='useOrdering'>Sorting type</label>
<select
id='useOrdering'
name='useOrdering'
value={formData.useOrdering}
onChange={(e) => inputChangeHandler(e)}
>
<option value='createdAt'>By creation date</option>
<option value='name'>Alphabetical order</option>
<option value='orderId'>Custom order</option>
</select>
</InputGroup>
<Button>Save changes</Button> <Button>Save changes</Button>
</form> </form>
) )
@ -124,4 +143,4 @@ const mapStateToProps = (state: GlobalState) => {
} }
} }
export default connect(mapStateToProps, { createNotification, updateConfig })(OtherSettings); export default connect(mapStateToProps, { createNotification, updateConfig, sortApps })(OtherSettings);

View file

@ -3,6 +3,7 @@ import { Model, Bookmark } from '.';
export interface Category extends Model { export interface Category extends Model {
name: string; name: string;
isPinned: boolean; isPinned: boolean;
orderId: number;
bookmarks: Bookmark[]; bookmarks: Bookmark[];
} }

View file

@ -7,7 +7,8 @@ import {
AddAppAction, AddAppAction,
DeleteAppAction, DeleteAppAction,
UpdateAppAction, UpdateAppAction,
ReorderAppAction, ReorderAppsAction,
SortAppsAction,
// Categories // Categories
GetCategoriesAction, GetCategoriesAction,
AddCategoryAction, AddCategoryAction,
@ -38,7 +39,8 @@ export enum ActionTypes {
addAppSuccess = 'ADD_APP_SUCCESS', addAppSuccess = 'ADD_APP_SUCCESS',
deleteApp = 'DELETE_APP', deleteApp = 'DELETE_APP',
updateApp = 'UPDATE_APP', updateApp = 'UPDATE_APP',
reorderApp = 'REORDER_APP', reorderApps = 'REORDER_APPS',
sortApps = 'SORT_APPS',
// Categories // Categories
getCategories = 'GET_CATEGORIES', getCategories = 'GET_CATEGORIES',
getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS', getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS',
@ -68,7 +70,8 @@ export type Action =
AddAppAction | AddAppAction |
DeleteAppAction | DeleteAppAction |
UpdateAppAction | UpdateAppAction |
ReorderAppAction | ReorderAppsAction |
SortAppsAction |
// Categories // Categories
GetCategoriesAction<any> | GetCategoriesAction<any> |
AddCategoryAction | AddCategoryAction |

View file

@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { ActionTypes } from './actionTypes'; import { ActionTypes } from './actionTypes';
import { App, ApiResponse, NewApp } from '../../interfaces'; import { App, ApiResponse, NewApp, Config } from '../../interfaces';
import { CreateNotificationAction } from './notification'; import { CreateNotificationAction } from './notification';
export interface GetAppsAction<T> { export interface GetAppsAction<T> {
@ -73,10 +73,13 @@ export const addApp = (formData: NewApp) => async (dispatch: Dispatch) => {
} }
}) })
dispatch<AddAppAction>({ await dispatch<AddAppAction>({
type: ActionTypes.addAppSuccess, type: ActionTypes.addAppSuccess,
payload: res.data.data payload: res.data.data
}) })
// Sort apps
dispatch<any>(sortApps())
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
@ -125,17 +128,20 @@ export const updateApp = (id: number, formData: NewApp) => async (dispatch: Disp
} }
}) })
dispatch<UpdateAppAction>({ await dispatch<UpdateAppAction>({
type: ActionTypes.updateApp, type: ActionTypes.updateApp,
payload: res.data.data payload: res.data.data
}) })
// Sort apps
dispatch<any>(sortApps())
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
} }
export interface ReorderAppAction { export interface ReorderAppsAction {
type: ActionTypes.reorderApp; type: ActionTypes.reorderApps;
payload: App[] payload: App[]
} }
@ -146,7 +152,7 @@ interface ReorderQuery {
}[] }[]
} }
export const reorderApp = (apps: App[]) => async (dispatch: Dispatch) => { export const reorderApps = (apps: App[]) => async (dispatch: Dispatch) => {
try { try {
const updateQuery: ReorderQuery = { apps: [] } const updateQuery: ReorderQuery = { apps: [] }
@ -157,11 +163,39 @@ export const reorderApp = (apps: App[]) => async (dispatch: Dispatch) => {
await axios.put<{}>('/api/apps/0/reorder', updateQuery); await axios.put<{}>('/api/apps/0/reorder', updateQuery);
dispatch<ReorderAppAction>({ dispatch<CreateNotificationAction>({
type: ActionTypes.reorderApp, type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: 'New order saved'
}
})
dispatch<ReorderAppsAction>({
type: ActionTypes.reorderApps,
payload: apps payload: apps
}) })
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
}
export interface SortAppsAction {
type: ActionTypes.sortApps;
payload: {};
}
export const sortApps = () => async (dispatch: Dispatch) => {
try {
const res = await axios.get<ApiResponse<Config>>('/api/config/useOrdering');
console.log(res.data.data);
dispatch<SortAppsAction>({
type: ActionTypes.sortApps,
payload: res.data.data.value
})
} catch (err) {
console.log(err);
}
} }

View file

@ -1,5 +1,6 @@
import { ActionTypes, Action } from '../actions'; import { ActionTypes, Action } from '../actions';
import { App } from '../../interfaces/App'; import { App } from '../../interfaces/App';
import { sortData } from '../../utility';
export interface State { export interface State {
loading: boolean; loading: boolean;
@ -52,15 +53,9 @@ const pinApp = (state: State, action: Action): State => {
} }
const addAppSuccess = (state: State, action: Action): State => { const addAppSuccess = (state: State, action: Action): State => {
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 { return {
...state, ...state,
apps: tmpApps apps: [...state.apps, action.payload]
} }
} }
@ -89,13 +84,22 @@ const updateApp = (state: State, action: Action): State => {
} }
} }
const reorderApp = (state: State, action: Action): State => { const reorderApps = (state: State, action: Action): State => {
return { return {
...state, ...state,
apps: action.payload apps: action.payload
} }
} }
const sortApps = (state: State, action: Action): State => {
const sortedApps = sortData<App>(state.apps, action.payload);
return {
...state,
apps: sortedApps
}
}
const appReducer = (state = initialState, action: Action) => { const appReducer = (state = initialState, action: Action) => {
switch (action.type) { switch (action.type) {
case ActionTypes.getApps: return getApps(state, action); case ActionTypes.getApps: return getApps(state, action);
@ -105,7 +109,8 @@ const appReducer = (state = initialState, action: Action) => {
case ActionTypes.addAppSuccess: return addAppSuccess(state, action); case ActionTypes.addAppSuccess: return addAppSuccess(state, action);
case ActionTypes.deleteApp: return deleteApp(state, action); case ActionTypes.deleteApp: return deleteApp(state, action);
case ActionTypes.updateApp: return updateApp(state, action); case ActionTypes.updateApp: return updateApp(state, action);
case ActionTypes.reorderApp: return reorderApp(state, action); case ActionTypes.reorderApps: return reorderApps(state, action);
case ActionTypes.sortApps: return sortApps(state, action);
default: return state; default: return state;
} }
} }

View file

@ -1,4 +1,5 @@
export * from './iconParser'; export * from './iconParser';
export * from './urlParser'; export * from './urlParser';
export * from './searchConfig'; export * from './searchConfig';
export * from './checkVersion'; export * from './checkVersion';
export * from './sortData';

View file

@ -0,0 +1,29 @@
interface Data {
name: string;
orderId: number;
createdAt: Date;
}
export const sortData = <T extends Data>(array: T[], field: string): T[] => {
const sortedData = array.slice();
if (field === 'name') {
sortedData.sort((a: T, b: T) => {
return a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })
})
} else if (field === 'orderId') {
sortedData.sort((a: T, b: T) => {
if (a.orderId < b.orderId) { return -1 }
if (a.orderId > b.orderId) { return 1 }
return 0;
})
} else {
sortedData.sort((a: T, b: T) => {
if (a.createdAt < b.createdAt) { return -1 }
if (a.createdAt > b.createdAt) { return 1 }
return 0;
})
}
return sortedData;
}

View file

@ -9,6 +9,11 @@ const Category = sequelize.define('Category', {
isPinned: { isPinned: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
defaultValue: false defaultValue: false
},
orderId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null
} }
}, { }, {
tableName: 'categories' tableName: 'categories'