From cb0b4b495f9fc19fdd817d83f842e998f32f6054 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 May 2021 18:23:12 +0200 Subject: [PATCH] Cleaned up Apps component. Delete App redux action. Apps edit mode with functionality do delete and pin apps --- client/src/App.tsx | 4 +- .../Apps/AppTable/AppTable.module.css | 62 ++++++++++++++ .../src/components/Apps/AppTable/AppTable.tsx | 67 +++++++++++++++ client/src/components/Apps/Apps.tsx | 18 +---- client/src/components/Home/Home.tsx | 81 ++++++++++++------- client/src/components/UI/Icon/Icon.tsx | 5 +- client/src/store/actions/actionTypes.ts | 8 +- client/src/store/actions/app.ts | 18 +++++ client/src/store/reducers/app.ts | 10 +++ controllers/apps.js | 4 +- 10 files changed, 225 insertions(+), 52 deletions(-) create mode 100644 client/src/components/Apps/AppTable/AppTable.module.css create mode 100644 client/src/components/Apps/AppTable/AppTable.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index b5fd09d..723fc54 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -12,7 +12,7 @@ import Apps from './components/Apps/Apps'; import Settings from './components/Settings/Settings'; if (localStorage.theme) { - store.dispatch(setTheme(localStorage.theme)); + store.dispatch(setTheme(localStorage.theme)); } const App = (): JSX.Element => { @@ -29,4 +29,4 @@ const App = (): JSX.Element => { ); } -export default App; +export default App; \ No newline at end of file diff --git a/client/src/components/Apps/AppTable/AppTable.module.css b/client/src/components/Apps/AppTable/AppTable.module.css new file mode 100644 index 0000000..2ccabb7 --- /dev/null +++ b/client/src/components/Apps/AppTable/AppTable.module.css @@ -0,0 +1,62 @@ +.TableContainer { + width: 100%; +} + +.Table { + border-collapse: collapse; + width: 100%; + text-align: left; + font-size: 16px; + color: var(--color-primary); +} + +.Table th, +.Table td { + /* border: 1px solid orange; */ + padding: 10px; +} + +/* Head */ + +.Table th { + --header-radius: 4px; + background-color: var(--color-primary); + color: var(--color-background); +} + +.Table th:first-child { + border-top-left-radius: var(--header-radius); + border-bottom-left-radius: var(--header-radius); +} + +.Table th:last-child { + border-top-right-radius: var(--header-radius); + border-bottom-right-radius: var(--header-radius); +} + +/* Body */ + +.Table td { + /* opacity: 0.5; */ + transition: all 0.2s; +} + +/* .Table td:hover { + opacity: 1; +} */ + +/* Actions */ + +.TableActions { + display: flex; + align-items: center; +} + + +.TableAction { + width: 22px; +} + +.TableAction:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/client/src/components/Apps/AppTable/AppTable.tsx b/client/src/components/Apps/AppTable/AppTable.tsx new file mode 100644 index 0000000..6798e1d --- /dev/null +++ b/client/src/components/Apps/AppTable/AppTable.tsx @@ -0,0 +1,67 @@ +import { connect } from 'react-redux'; +import { App, GlobalState } from '../../../interfaces'; +import { pinApp, deleteApp } from '../../../store/actions'; + +import classes from './AppTable.module.css'; +import Icon from '../../UI/Icon/Icon'; + +interface ComponentProps { + apps: App[]; + pinApp: (id: number, isPinned: boolean) => void; + deleteApp: (id: number) => void; +} + +const AppTable = (props: ComponentProps): JSX.Element => { + const deleteAppHandler = (app: App): void => { + const proceed = window.confirm(`Are you sure you want to delete ${app.name} at ${app.url} ?`); + + if (proceed) { + props.deleteApp(app.id); + } + } + + return ( +
+ + + + + + + + + + + {props.apps.map((app: App): JSX.Element => { + return ( + + + + + + + ) + })} + +
NameUrlIconActions
{app.name}{app.url}{app.icon} +
deleteAppHandler(app)}> + +
+
+
props.pinApp(app.id, app.isPinned)}> + {app.isPinned? : } +
+
+
+ ) +} + +const mapStateToProps = (state: GlobalState) => { + return { + apps: state.app.apps + } +} + +export default connect(mapStateToProps, { pinApp, deleteApp })(AppTable); \ No newline at end of file diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx index 0bb6d64..afe4290 100644 --- a/client/src/components/Apps/Apps.tsx +++ b/client/src/components/Apps/Apps.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; // Redux @@ -22,12 +22,11 @@ import Modal from '../UI/Modal/Modal'; import AppGrid from './AppGrid/AppGrid'; import AppForm from './AppForm/AppForm'; import AppTable from './AppTable/AppTable'; -import Test from '../Test'; interface ComponentProps { getApps: Function; - pinApp: (id: number, isPinned: boolean) => any; - addApp: (formData: NewApp) => any; + pinApp: (id: number, isPinned: boolean) => void; + addApp: (formData: NewApp) => void; apps: App[]; loading: boolean; } @@ -38,17 +37,8 @@ const Apps = (props: ComponentProps): JSX.Element => { useEffect(() => { props.getApps(); - // props.addApp({ - // name: 'Plex', - // url: '192.168.0.128', - // icon: 'cat' - // }) }, [props.getApps]); - const pinAppHandler = (id: number, state: boolean): void => { - props.pinApp(id, state); - } - const toggleModal = (): void => { setModalIsOpen(!modalIsOpen); } @@ -83,7 +73,7 @@ const Apps = (props: ComponentProps): JSX.Element => {
{props.loading - ? 'loading' + ? : (!isInEdit ? : ) diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index 55c1025..bf561a4 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -1,55 +1,64 @@ +import { useEffect } from 'react'; import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { GlobalState } from '../../interfaces/GlobalState'; +import { getApps } from '../../store/actions'; + import Icon from '../UI/Icon/Icon'; +import Modal from '../UI/Modal/Modal'; import classes from './Home.module.css'; import { Container } from '../UI/Layout/Layout'; import Headline from '../UI/Headlines/Headline/Headline'; import SectionHeadline from '../UI/Headlines/SectionHeadline/SectionHeadline'; import Apps from '../Apps/Apps'; +import AppGrid from '../Apps/AppGrid/AppGrid'; +import { App } from '../../interfaces'; +import Spinner from '../UI/Spinner/Spinner'; + +interface ComponentProps { + getApps: Function; + loading: boolean; + apps: App[]; +} + +const Home = (props: ComponentProps): JSX.Element => { + useEffect(() => { + props.getApps(); + }, [props.getApps]); -const Home = (): JSX.Element => { const dateAndTime = (): string => { const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const now = new Date(); - return `${days[now.getDay()]}, ${now.getDate()} of ${months[now.getMonth()]} ${now.getFullYear()}`; + const ordinal = (day: number): string => { + if (day > 3 && day < 21) return 'th'; + switch (day % 10) { + case 1: return "st"; + case 2: return "nd"; + case 3: return "rd"; + default: return "th"; + } + } + + return `${days[now.getDay()]}, ${now.getDate()}${ordinal(now.getDate())} ${months[now.getMonth()]} ${now.getFullYear()}`; } const greeter = (): string => { const now = new Date().getHours(); let msg: string; - if (now > 18) { - msg = 'Good evening!'; - } else if (now > 12) { - msg = 'Good afternoon!'; - } else if (now > 6) { - msg = 'Good morning!'; - } else if (now > 0) { - msg = 'Good night!'; - } else { - msg = 'Hello!'; - } + if (now > 18) msg = 'Good evening!'; + else if (now > 12) msg = 'Good afternoon!'; + else if (now > 6) msg = 'Good morning!'; + else if (now > 0) msg = 'Good night!'; + else msg = 'Hello!'; return msg; } - (() => { - const mdiName = 'book-open-blank-variant'; - const expected = 'mdiBookOpenBlankVariant'; - - let parsedName = mdiName - .split('-') - .map((word: string) => `${word[0].toUpperCase()}${word.slice(1)}`) - .join(''); - parsedName = `mdi${parsedName}`; - - console.log(parsedName); - console.log(parsedName === expected); - })(); - return (
@@ -57,10 +66,13 @@ const Home = (): JSX.Element => {

{greeter()}

- - + + {props.loading + ? + : app.isPinned)} /> + } - + @@ -69,4 +81,11 @@ const Home = (): JSX.Element => { ) } -export default Home; \ No newline at end of file +const mapStateToProps = (state: GlobalState) => { + return { + loading: state.app.loading, + apps: state.app.apps + } +} + +export default connect(mapStateToProps, { getApps })(Home); \ No newline at end of file diff --git a/client/src/components/UI/Icon/Icon.tsx b/client/src/components/UI/Icon/Icon.tsx index 2ea4350..6924086 100644 --- a/client/src/components/UI/Icon/Icon.tsx +++ b/client/src/components/UI/Icon/Icon.tsx @@ -4,6 +4,7 @@ import { Icon as MDIcon } from '@mdi/react'; interface ComponentProps { icon: string; + color?: string; } const Icon = (props: ComponentProps): JSX.Element => { @@ -16,8 +17,10 @@ const Icon = (props: ComponentProps): JSX.Element => { } return ( - ) } diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts index 250201d..92041be 100644 --- a/client/src/store/actions/actionTypes.ts +++ b/client/src/store/actions/actionTypes.ts @@ -2,7 +2,8 @@ import { GetAppsAction, SetTheme, PinAppAction, - AddAppAction + AddAppAction, + DeleteAppAction } from './'; export enum ActionTypes { @@ -12,7 +13,8 @@ export enum ActionTypes { getAppsError = 'GET_APPS_ERROR', pinApp = 'PIN_APP', addApp = 'ADD_APP', - addAppSuccess = 'ADD_APP_SUCCESS' + addAppSuccess = 'ADD_APP_SUCCESS', + deleteApp = 'DELETE_APP' } -export type Action = GetAppsAction | SetTheme | PinAppAction | AddAppAction; \ No newline at end of file +export type Action = GetAppsAction | SetTheme | PinAppAction | AddAppAction | DeleteAppAction; \ No newline at end of file diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts index 411f825..74fb079 100644 --- a/client/src/store/actions/app.ts +++ b/client/src/store/actions/app.ts @@ -63,4 +63,22 @@ export const addApp = (formData: NewApp) => async (dispatch: Dispatch) => { } catch (err) { console.log(err); } +} + +export interface DeleteAppAction { + type: ActionTypes.deleteApp, + payload: number +} + +export const deleteApp = (id: number) => async (dispatch: Dispatch) => { + try { + const res = await axios.delete>(`/api/apps/${id}`); + + dispatch({ + type: ActionTypes.deleteApp, + payload: id + }) + } catch (err) { + console.log(err); + } } \ No newline at end of file diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts index 06678ca..36c93e7 100644 --- a/client/src/store/reducers/app.ts +++ b/client/src/store/reducers/app.ts @@ -60,6 +60,15 @@ const addAppSuccess = (state: State, action: Action): State => { } } +const deleteApp = (state: State, action: Action): State => { + const tmpApps = [...state.apps].filter((app: App) => app.id !== action.payload); + + return { + ...state, + apps: tmpApps + } +} + const appReducer = (state = initialState, action: Action) => { switch (action.type) { case ActionTypes.getApps: return getApps(state, action); @@ -67,6 +76,7 @@ const appReducer = (state = initialState, action: Action) => { case ActionTypes.getAppsError: return getAppsError(state, action); case ActionTypes.pinApp: return pinApp(state, action); case ActionTypes.addAppSuccess: return addAppSuccess(state, action); + case ActionTypes.deleteApp: return deleteApp(state, action); default: return state; } } diff --git a/controllers/apps.js b/controllers/apps.js index 68d34f2..496fa54 100644 --- a/controllers/apps.js +++ b/controllers/apps.js @@ -18,7 +18,9 @@ exports.createApp = asyncWrapper(async (req, res, next) => { // @route GET /api/apps // @access Public exports.getApps = asyncWrapper(async (req, res, next) => { - const apps = await App.findAll(); + const apps = await App.findAll({ + order: [['name', 'ASC']] + }); res.status(200).json({ success: true,