diff --git a/client/Store.ts b/client/Store.ts index 177bf22..c7aadcd 100644 --- a/client/Store.ts +++ b/client/Store.ts @@ -1,5 +1,7 @@ +import {ContactsStore} from '@client/admin/users/store/ContactsStore'; import {ExampleStore} from '@client/admin/example/store'; export interface Store { example: ExampleStore; + contacts: ContactsStore; } diff --git a/client/admin/users/actions/ContactsActions.ts b/client/admin/users/actions/ContactsActions.ts new file mode 100644 index 0000000..f8b4d14 --- /dev/null +++ b/client/admin/users/actions/ContactsActions.ts @@ -0,0 +1,31 @@ +import {bindActionCreators, Dispatch} from 'redux'; +import {Action} from 'redux-actions'; + +const PREFIX = 'USERS_MODUL_'; + +export const ContactsActionType = { + DELETE_CONTACT: `${PREFIX}DELETE_CONTACT`, + ID: `${PREFIX}ID`, + EMPTY_CONTACT: `${PREFIX}EMPTY_CONTACT`, + ISOPEN: `${PREFIX}ISOPEN`, + ID_CONTACT: `${PREFIX}ID_CONTACT`, + HANDLE_CONTACT: `${PREFIX}HANDLE_CONTACT`, + SAVE_CONTACT: `${PREFIX}SAVE_CONTACT`, +}; + +const ContactsActionDispatch = { + isOpen: (): Action => ({type: ContactsActionType.ISOPEN}), + contact: (): Action => ({type: ContactsActionType.EMPTY_CONTACT, payload: ''}), + deleteContact: (id: number): Action => ({type: ContactsActionType.DELETE_CONTACT, payload: id}), + idContact: (id: number): Action => ({type: ContactsActionType.ID_CONTACT, payload: id}), + id: (id: number): Action => ({type: ContactsActionType.ID, payload: id}), + handleOnChange: (e: React.ChangeEvent): Action> => ({ + type: ContactsActionType.HANDLE_CONTACT, + payload: e, + }), + saveContact: (): Action => ({type: ContactsActionType.SAVE_CONTACT, payload: ''}), +}; + +export type ContactsAction = typeof ContactsActionDispatch; + +export const ContactsActionCreator = (dispatch: Dispatch) => bindActionCreators(ContactsActionDispatch, dispatch); diff --git a/client/admin/users/actions/index.ts b/client/admin/users/actions/index.ts new file mode 100644 index 0000000..db44149 --- /dev/null +++ b/client/admin/users/actions/index.ts @@ -0,0 +1 @@ +export * from './ContactsActions'; diff --git a/client/admin/users/components/ContactList.tsx b/client/admin/users/components/ContactList.tsx new file mode 100644 index 0000000..04e65dd --- /dev/null +++ b/client/admin/users/components/ContactList.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import {TableRow, TableCell} from '@material-ui/core'; +import {ContactModel} from './model'; +import {EditButton} from './buttons/EditButton'; +import {DeleteButton} from './buttons/DeleteButton'; + +interface Props { + contact: ContactModel; + deleteContact: (id: number) => () => void; + handleOnUserId: (id: number) => () => void; +} + +export const ContactList: React.SFC = ({contact, deleteContact, handleOnUserId}) => ( + + {contact.firstName} + {contact.lastName} + {contact.email} + {contact.phoneNumber} + + + + + +); diff --git a/client/admin/users/components/ContactsList.tsx b/client/admin/users/components/ContactsList.tsx new file mode 100644 index 0000000..b49d2f1 --- /dev/null +++ b/client/admin/users/components/ContactsList.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import {Paper, Table, TableHead, TableRow, TableCell, TableBody, withStyles} from '@material-ui/core'; +import {ContactModel} from './model'; +import {ContactList} from './ContactList'; + +interface Props { + contacts: ContactModel[]; + deleteContact: (id: number) => () => void; + handleOnUserId: (id: number) => () => void; + nadpis?: string; +} + +// type WithDataProps = Pick; + +const decorate = withStyles(() => ({ + paper: { + margin: '0 10px', + }, +})); + +const ContactsListData = decorate(({nadpis, classes, contacts, deleteContact, handleOnUserId}) => ( + + + + + First Name + Last Name + E-mail + Phone number + {nadpis} + + + + {contacts.map((contact: ContactModel) => ( + + ))} + +
+
+)); + +const WithDataComponent = (BaseComponent: React.ComponentType): React.ComponentClass => { + const initialState = { + nadpis: 'Icons', + }; + return class extends React.Component { + readonly state = initialState; + render() { + const {nadpis} = this.state; + return ; + } + }; +}; + +export const ContactsList = WithDataComponent(ContactsListData); diff --git a/client/admin/users/components/SimpleModal.tsx b/client/admin/users/components/SimpleModal.tsx new file mode 100644 index 0000000..cf1e020 --- /dev/null +++ b/client/admin/users/components/SimpleModal.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import {TextField, withStyles} from '@material-ui/core'; +import Modal from '@material-ui/core/Modal'; +import Slide from '@material-ui/core/Slide'; +import Typography from '@material-ui/core/Typography'; +import {CancelButton} from './buttons/CancelButton'; +import {ContactModel} from './model'; +import {SubmitButton} from './buttons/SubmitButton'; + +interface Props { + isOpen: boolean; + handleOnClose: () => void; + contact: ContactModel; + handleOnChange: (e: React.ChangeEvent) => void; + handleOnSave: () => void; +} + +const decorate = withStyles(() => ({ + modal: { + top: '25%', + margin: 'auto', + width: '400px', + backgroundColor: 'white', + padding: '20px', + }, + modal_window: { + alignItems: 'center', + justifyContent: 'center', + }, +})); + +export const SimpleModal = decorate(({classes, isOpen, handleOnClose, contact, handleOnChange, handleOnSave}) => { + return ( + + +
+ + Add new user to database + + +
+ +
+ +
+ +
+ + +
+
+
+ ); +}); diff --git a/client/admin/users/components/UsersIndexPage.tsx b/client/admin/users/components/UsersIndexPage.tsx index e8b1a53..217652d 100644 --- a/client/admin/users/components/UsersIndexPage.tsx +++ b/client/admin/users/components/UsersIndexPage.tsx @@ -1,8 +1,72 @@ import * as React from 'react'; import {WithAdminProps} from '@client/with/withAdmin'; +import {SimpleModal} from '@client/admin/users/components/SimpleModal'; +import {ContactsList} from '@client/admin/users/components/ContactsList'; +import {AddButton} from './buttons/AddButton'; +import {ContactsStore} from '../store'; +import {connect} from 'react-redux'; +import {Store} from '@client/Store'; +import {ContactsActionCreator, ContactsAction} from '../actions'; + +interface OwnProps extends WithAdminProps {} + +interface ConnectedState { + readonly contacts: ContactsStore; +} + +interface ConnectedDispatch extends ContactsAction {} + +type Props = ConnectedState & ConnectedDispatch & OwnProps; + +class UsersPage extends React.Component { + handleOnOpen = () => { + this.props.isOpen(); + }; + + handleOnClose = () => { + this.props.isOpen(); + this.props.contact(); + }; + + handleOnDelete = (id: number) => () => { + this.props.deleteContact(id); + }; + + handleOnUserId = (id: number) => () => { + this.props.id(id); + this.props.isOpen(); + this.props.idContact(id); + }; + + handleOnChange = (e: any) => { + this.props.handleOnChange(e); + }; + + handleOnSave = () => { + this.props.saveContact(); + }; -export class UsersIndexPage extends React.Component { render() { - return
UsersIndexPage
; + const { + contacts: {contacts, isOpen, contact}, + } = this.props; + return ( + <> + + + + + ); } } + +export const UsersIndexPage = connect( + ({contacts}: Store) => ({contacts}), + ContactsActionCreator, +)(UsersPage); diff --git a/client/admin/users/components/buttons/AddButton.tsx b/client/admin/users/components/buttons/AddButton.tsx new file mode 100644 index 0000000..cc8b628 --- /dev/null +++ b/client/admin/users/components/buttons/AddButton.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import {Add} from '@material-ui/icons'; +import {Button, withStyles} from '@material-ui/core'; + +interface Props { + handleOnOpen: () => void; +} + +const decorate = withStyles(() => ({ + button: { + margin: '10px', + }, +})); + +export const AddButton = decorate(({classes, handleOnOpen}) => { + return ( + + ); +}); diff --git a/client/admin/users/components/buttons/CancelButton.tsx b/client/admin/users/components/buttons/CancelButton.tsx new file mode 100644 index 0000000..651432f --- /dev/null +++ b/client/admin/users/components/buttons/CancelButton.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import {Button, withStyles} from '@material-ui/core'; + +interface Props { + handleOnClose: () => void; +} + +const decorate = withStyles(() => ({ + button: { + margin: '10px', + }, +})); + +export const CancelButton = decorate(({classes, handleOnClose}) => { + return ( + + ); +}); diff --git a/client/admin/users/components/buttons/DeleteButton.tsx b/client/admin/users/components/buttons/DeleteButton.tsx new file mode 100644 index 0000000..144db77 --- /dev/null +++ b/client/admin/users/components/buttons/DeleteButton.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import {Delete as DeleteIcon} from '@material-ui/icons'; +import {Button, withStyles} from '@material-ui/core'; +import {ContactModel} from '@client/admin/users/components/model'; + +interface Props { + contact: ContactModel; + deleteContact: (id: number) => () => void; +} + +const decorate = withStyles(() => ({ + button_edit: { + margin: '10px', + }, +})); + +export const DeleteButton = decorate(({deleteContact, contact}) => { + return ( + + ); +}); diff --git a/client/admin/users/components/buttons/EditButton.tsx b/client/admin/users/components/buttons/EditButton.tsx new file mode 100644 index 0000000..f968043 --- /dev/null +++ b/client/admin/users/components/buttons/EditButton.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Icon from '@material-ui/core/Icon'; +import {Button, withStyles} from '@material-ui/core'; +import {ContactModel} from '@client/admin/users/components/model'; + +interface Props { + contact: ContactModel; + handleOnUserId: (id: number) => () => void; +} + +const decorate = withStyles(() => ({ + button_edit: { + margin: '10px', + }, +})); + +export const EditButton = decorate(({classes, handleOnUserId, contact}) => { + return ( + + ); +}); diff --git a/client/admin/users/components/buttons/SubmitButton.tsx b/client/admin/users/components/buttons/SubmitButton.tsx new file mode 100644 index 0000000..954ec11 --- /dev/null +++ b/client/admin/users/components/buttons/SubmitButton.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import {Button, withStyles} from '@material-ui/core'; + +interface Props { + handleOnSave: () => void; +} + +const decorate = withStyles(() => ({ + button: { + margin: '10px', + }, +})); + +export const SubmitButton = decorate(({handleOnSave}) => { + return ( + + ); +}); diff --git a/client/admin/users/components/model/index.ts b/client/admin/users/components/model/index.ts new file mode 100644 index 0000000..901faad --- /dev/null +++ b/client/admin/users/components/model/index.ts @@ -0,0 +1 @@ +export * from './initialState'; diff --git a/client/admin/users/components/model/initialState.ts b/client/admin/users/components/model/initialState.ts new file mode 100644 index 0000000..108b47f --- /dev/null +++ b/client/admin/users/components/model/initialState.ts @@ -0,0 +1,31 @@ +export const contactsList = [ + { + id: 1, + firstName: 'Petr', + lastName: 'Novak', + email: 'pronovaso@icloud.com', + phoneNumber: 777123456, + }, + { + id: 2, + firstName: 'Karel', + lastName: 'Omacka', + email: 'karel@gmail.com', + phoneNumber: 775345234, + }, + { + id: 3, + firstName: 'Martina', + lastName: 'Leva', + email: 'martina@seznam.cz', + phoneNumber: 603100800, + }, +] as ContactModel[]; + +export interface ContactModel { + id: number; + firstName: string; + lastName: string; + email: string; + phoneNumber: number | string; +} diff --git a/client/admin/users/reducers/ContactsReducer.ts b/client/admin/users/reducers/ContactsReducer.ts new file mode 100644 index 0000000..9631d4d --- /dev/null +++ b/client/admin/users/reducers/ContactsReducer.ts @@ -0,0 +1,69 @@ +import {contactsList} from './../components/model/initialState'; +import {handleActions} from 'redux-actions'; +import {ContactsActionType as ActionType} from '../actions'; +import {ContactsStore as Store} from '../store'; + +const initialState = { + contacts: contactsList, + contact: {}, + isOpen: false, + id: 0, +} as Store; + +export const ContactsReducer = handleActions( + { + [ActionType.ISOPEN]: (state: Store): Store => ({ + ...state, + isOpen: state.isOpen === false ? true : false, + }), + [ActionType.EMPTY_CONTACT]: (state: Store, {payload}): Store => ({ + ...state, + contact: payload, + }), + [ActionType.DELETE_CONTACT]: (state: Store, {payload}): Store => ({ + ...state, + contacts: [...state.contacts.filter((contact) => contact.id !== payload)], + }), + [ActionType.ID_CONTACT]: (state: Store, {payload}): Store => { + const user = state.contacts.filter((contact) => contact.id === payload).find((contact) => contact.id === payload); + if (user !== undefined) { + return { + ...state, + contact: user, + }; + } + return state; + }, + [ActionType.HANDLE_CONTACT]: (state: Store, {payload}): Store => ({ + ...state, + contact: {...state.contact, [payload.target.name]: payload.target.value}, + }), + [ActionType.ID]: (state: Store, {payload}): Store => ({ + ...state, + id: payload, + }), + [ActionType.SAVE_CONTACT]: (state: Store, {payload}): any => { + const userId = new Date().getMilliseconds(); + const {contact, id} = state; + const contactsMap = state.contacts.map((user) => (user.id === id ? {...user, ...contact} : user)); + const contactFilter = contactsMap.filter((contacts) => contacts); + if (id > 0) { + return { + ...state, + contacts: contactFilter, + isOpen: false, + contact: payload, + }; + } + if (id === 0) { + return { + ...state, + contacts: [...state.contacts, {...contact, id: userId}], + isOpen: false, + contact: payload, + }; + } + }, + }, + initialState, +); diff --git a/client/admin/users/reducers/index.ts b/client/admin/users/reducers/index.ts new file mode 100644 index 0000000..22c08ed --- /dev/null +++ b/client/admin/users/reducers/index.ts @@ -0,0 +1 @@ +export * from './ContactsReducer'; diff --git a/client/admin/users/store/ContactsStore.ts b/client/admin/users/store/ContactsStore.ts new file mode 100644 index 0000000..f848e64 --- /dev/null +++ b/client/admin/users/store/ContactsStore.ts @@ -0,0 +1,8 @@ +import {ContactModel} from '@client/admin/users/components/model'; + +export interface ContactsStore { + contacts: ContactModel[]; + contact: ContactModel; + isOpen: boolean; + id: number; +} diff --git a/client/admin/users/store/index.ts b/client/admin/users/store/index.ts new file mode 100644 index 0000000..3400fcd --- /dev/null +++ b/client/admin/users/store/index.ts @@ -0,0 +1 @@ +export * from './ContactsStore'; diff --git a/client/rootReducer.ts b/client/rootReducer.ts index 1cc1cfa..0bc7a2e 100644 --- a/client/rootReducer.ts +++ b/client/rootReducer.ts @@ -1,3 +1,4 @@ +import {ContactsReducer} from './admin/users/reducers/ContactsReducer'; import {combineReducers} from 'redux'; import {Store} from '@client/Store'; import {ExampleReducer} from '@client/admin/example/reducers'; @@ -6,4 +7,5 @@ type Reducers = {[P in keyof Store]: any}; export const rootReducer = combineReducers({ example: ExampleReducer, + contacts: ContactsReducer, });