Table component. Bookmarks edit view

This commit is contained in:
unknown 2021-05-25 11:44:11 +02:00
parent 4eaf9659d1
commit bd5354a2e3
12 changed files with 281 additions and 137 deletions

View file

@ -1,58 +1,8 @@
.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;
}

View file

@ -5,6 +5,7 @@ import { pinApp, deleteApp } from '../../../store/actions';
import classes from './AppTable.module.css';
import Icon from '../../UI/Icons/Icon/Icon';
import Table from '../../UI/Table/Table';
interface ComponentProps {
apps: App[];
@ -29,55 +30,48 @@ const AppTable = (props: ComponentProps): JSX.Element => {
}
return (
<div className={classes.TableContainer}>
<table className={classes.Table}>
<thead className={classes.TableHead}>
<tr>
<th>Name</th>
<th>Url</th>
<th>Icon</th>
<th>Actions</th>
<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>
</thead>
<tbody className={classes.TableBody}>
{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>
)
})}
</tbody>
</table>
</div>
)
})}
</Table>
)
}

View file

@ -21,7 +21,8 @@
transition: all 0.25s;
}
.BookmarkCard a:hover {
.BookmarkCard a:hover,
.BookmarkCard a:focus {
text-decoration: underline;
padding-left: 10px;
}

View file

@ -4,12 +4,12 @@ import { connect } from 'react-redux';
import ModalForm from '../../UI/Forms/ModalForm/ModalForm';
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
import { Category, GlobalState, NewBookmark, NewCategory } from '../../../interfaces';
import { FormContentType } from '../Bookmarks';
import { ContentType } from '../Bookmarks';
import { getCategories, addCategory, addBookmark } from '../../../store/actions';
interface ComponentProps {
modalHandler: () => void;
contentType: FormContentType;
contentType: ContentType;
categories: Category[];
addCategory: (formData: NewCategory) => void;
addBookmark: (formData: NewBookmark) => void;
@ -29,10 +29,10 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
const formSubmitHandler = (e: SyntheticEvent<HTMLFormElement>): void => {
e.preventDefault();
if (props.contentType === FormContentType.category) {
if (props.contentType === ContentType.category) {
props.addCategory(categoryName);
setCategoryName({ name: '' });
} else if (props.contentType === FormContentType.bookmark) {
} else if (props.contentType === ContentType.bookmark) {
if (formData.categoryId === -1) {
alert('select category');
return;
@ -66,7 +66,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
modalHandler={props.modalHandler}
formHandler={formSubmitHandler}
>
{props.contentType === FormContentType.category
{props.contentType === ContentType.category
? (
<Fragment>
<InputGroup>

View file

@ -0,0 +1,12 @@
.TableActions {
display: flex;
align-items: center;
}
.TableAction {
width: 22px;
}
.TableAction:hover {
cursor: pointer;
}

View file

@ -0,0 +1,103 @@
import { ContentType } from '../Bookmarks';
import classes from './BookmarkTable.module.css';
import Table from '../../UI/Table/Table';
import { Bookmark, Category } from '../../../interfaces';
import Icon from '../../UI/Icons/Icon/Icon';
interface ComponentProps {
contentType: ContentType;
categories: Category[];
}
const BookmarkTable = (props: ComponentProps): JSX.Element => {
if (props.contentType === ContentType.category) {
return (
<Table headers={[
'Name',
'Actions'
]}>
{props.categories.map((category: Category) => {
return (
<tr key={category.id}>
<td>{category.name}</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}>
{category.isPinned
? <Icon icon='mdiPinOff' color='var(--color-accent)' />
: <Icon icon='mdiPin' />
}
</div>
</td>
</tr>
)
})}
</Table>
)
} else {
const bookmarks: {bookmark: Bookmark, categoryName: string}[] = [];
props.categories.forEach((category: Category) => {
category.bookmarks.forEach((bookmark: Bookmark) => {
bookmarks.push({
bookmark,
categoryName: category.name
});
})
})
return (
<Table headers={[
'Name',
'URL',
'Category',
'Actions'
]}>
{bookmarks.map((bookmark: {bookmark: Bookmark, categoryName: string}) => {
return (
<tr key={bookmark.bookmark.id}>
<td>{bookmark.bookmark.name}</td>
<td>{bookmark.bookmark.url}</td>
<td>{bookmark.categoryName}</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>
</td>
</tr>
)
})}
</Table>
)
}
}
export default BookmarkTable;

View file

@ -14,6 +14,7 @@ import { Category, GlobalState } from '../../interfaces';
import Spinner from '../UI/Spinner/Spinner';
import Modal from '../UI/Modal/Modal';
import BookmarkForm from './BookmarkForm/BookmarkForm';
import BookmarkTable from './BookmarkTable/BookmarkTable';
interface ComponentProps {
loading: boolean;
@ -21,14 +22,16 @@ interface ComponentProps {
getCategories: () => void;
}
export enum FormContentType {
export enum ContentType {
category,
bookmark
}
const Bookmarks = (props: ComponentProps): JSX.Element => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [formContentType, setFormContentType] = useState(FormContentType.category);
const [formContentType, setFormContentType] = useState(ContentType.category);
const [isInEdit, setIsInEdit] = useState(false);
const [tableContentType, setTableContentType] = useState(ContentType.category);
useEffect(() => {
if (props.categories.length === 0) {
@ -40,24 +43,29 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
setModalIsOpen(!modalIsOpen);
}
const addActionHandler = (contentType: FormContentType) => {
const addActionHandler = (contentType: ContentType) => {
setFormContentType(contentType);
toggleModal();
}
const toggleEdit = (): void => {
setIsInEdit(!isInEdit);
}
const editActionHandler = (contentType: ContentType) => {
// We're in the edit mode and the same button was clicked - go back to list
if (isInEdit && contentType === tableContentType) {
setIsInEdit(false);
} else {
setIsInEdit(true);
setTableContentType(contentType);
}
}
return (
<Container>
<Modal isOpen={modalIsOpen} setIsOpen={toggleModal}>
{formContentType === FormContentType.category
? <BookmarkForm
modalHandler={toggleModal}
contentType={FormContentType.category}
/>
: <BookmarkForm
modalHandler={toggleModal}
contentType={FormContentType.bookmark}
/>
}
<BookmarkForm modalHandler={toggleModal} contentType={formContentType} />
</Modal>
<Headline
@ -69,26 +77,30 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
<ActionButton
name='Add Category'
icon='mdiPlusBox'
handler={() => addActionHandler(FormContentType.category)}
handler={() => addActionHandler(ContentType.category)}
/>
<ActionButton
name='Add Bookmark'
icon='mdiPlusBox'
handler={() => addActionHandler(FormContentType.bookmark)}
handler={() => addActionHandler(ContentType.bookmark)}
/>
<ActionButton
name='Edit Categories'
icon='mdiPencil'
handler={() => editActionHandler(ContentType.category)}
/>
<ActionButton
name='Edit Bookmarks'
icon='mdiPencil'
handler={() => editActionHandler(ContentType.bookmark)}
/>
</div>
{props.loading
? <Spinner />
: <BookmarkGrid categories={props.categories} />
: (!isInEdit
? <BookmarkGrid categories={props.categories} />
: <BookmarkTable contentType={tableContentType} categories={props.categories} />)
}
</Container>
)

View file

@ -1,6 +1,10 @@
import classes from './Layout.module.css';
export const Container = (props: any): JSX.Element => {
interface ComponentProps {
children: JSX.Element | JSX.Element[];
}
export const Container = (props: ComponentProps): JSX.Element => {
return (
<div className={classes.Container}>
{props.children}

View file

@ -0,0 +1,41 @@
.TableContainer {
width: 100%;
}
.Table {
border-collapse: collapse;
width: 100%;
text-align: left;
font-size: 16px;
color: var(--color-primary);
}
.Table th,
.Table td {
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;
}

View file

@ -0,0 +1,25 @@
import classes from './Table.module.css';
interface ComponentProps {
children: JSX.Element | JSX.Element[];
headers: string[];
}
const Table = (props: ComponentProps): JSX.Element => {
return (
<div className={classes.TableContainer}>
<table className={classes.Table}>
<thead className={classes.TableHead}>
<tr>
{props.headers.map((header: string, index: number): JSX.Element => (<th key={index}>{header}</th>))}
</tr>
</thead>
<tbody className={classes.TableBody}>
{props.children}
</tbody>
</table>
</div>
)
}
export default Table;

View file

@ -2,7 +2,6 @@
margin: 0;
padding: 0;
box-sizing: border-box;
/* transition: all 0.3s; */
user-select: none;
}
@ -14,13 +13,6 @@ body {
background-color: var(--color-background);
transition: background-color 0.3s;
/* font weights
light 300
regular 400
semi-bold 600
bold 700
extra-bold 800
*/
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif;
font-size: 14px;
}
@ -28,11 +20,4 @@ body {
a {
color: var(--color-primary);
text-decoration: none;
/* opacity: 0.75; */
}
/* 320px 480px: Mobile devices.
481px 768px: iPads, Tablets.
769px 1024px: Small screens, laptops.
1025px 1200px: Desktops, large screens.
1201px and more Extra large screens, TV. */
}

View file

@ -2,6 +2,7 @@ import axios from 'axios';
import { Dispatch } from 'redux';
import { ActionTypes } from './actionTypes';
import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces';
import { CreateNotificationAction } from './notification';
export interface GetCategoriesAction<T> {
type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError;
@ -35,6 +36,14 @@ export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch)
try {
const res = await axios.post<ApiResponse<Category>>('/api/categories', formData);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Category ${formData.name} created`
}
})
dispatch<AddCategoryAction>({
type: ActionTypes.addCategory,
payload: res.data.data
@ -53,6 +62,14 @@ export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch)
try {
const res = await axios.post<ApiResponse<Bookmark>>('/api/bookmarks', formData);
dispatch<CreateNotificationAction>({
type: ActionTypes.createNotification,
payload: {
title: 'Success',
message: `Bookmark ${formData.name} created`
}
})
dispatch<AddBookmarkAction>({
type: ActionTypes.addBookmark,
payload: res.data.data