diff --git a/18 Normalizr/package.json b/18 Normalizr/package.json index d0cd2b6..b23c5b0 100644 --- a/18 Normalizr/package.json +++ b/18 Normalizr/package.json @@ -35,7 +35,7 @@ "bootstrap": "^3.3.7", "jquery": "^3.1.1", "lc-form-validation": "^0.1.7", - "normalizr": "^2.3.1", + "normalizr": "^3.0.0", "react": "^15.3.2", "react-dom": "^15.3.2", "react-redux": "^4.4.5", @@ -43,6 +43,7 @@ "react-router-redux": "^4.0.6", "redux": "^3.6.0", "redux-thunk": "^2.1.0", + "reselect": "^2.5.4", "toastr": "^2.1.2", "validator": "^6.2.0" } diff --git a/18 Normalizr/src/common/actionsEnums.ts b/18 Normalizr/src/common/actionsEnums.ts index 145aa5c..7aa8180 100644 --- a/18 Normalizr/src/common/actionsEnums.ts +++ b/18 Normalizr/src/common/actionsEnums.ts @@ -3,8 +3,9 @@ export const actionsEnums = { USERPROFILE_PERFORM_LOGIN: "USERPROFILE_PERFORM_LOGIN", STUDENTS_GET_LIST_REQUEST_COMPLETED: "STUDENTS_GET_LIST_REQUEST_COMPLETED", STUDENT_GET_STUDENT_REQUEST_COMPLETED: "STUDENT_GET_STUDENT_REQUEST_COMPLETED", - STUDENT_FIELD_VALUE_CHANGED: "STUDENT_FIELD_VALUE_CHANGED", STUDENT_FIELD_VALUE_CHANGED_COMPLETED: "STUDENT_FIELD_VALUE_CHANGED_COMPLETED", STUDENT_SAVE_COMPLETED: "STUDENT_SAVE_COMPLETED", - RESET_EDITING_STUDENT: "RESET_EDITING_STUDENT" + RESET_EDITING_STUDENT: "RESET_EDITING_STUDENT", + FETCH_COUNTRY_LIST_REQUEST_COMPLETED: "FETCH_COUNTRY_LIST_REQUEST_COMPLETED", + ADD_COUNTRY: "ADD_COUNTRY", }; diff --git a/18 Normalizr/src/common/components/Input.tsx b/18 Normalizr/src/common/components/Input.tsx index a0b55a4..2cc8c09 100644 --- a/18 Normalizr/src/common/components/Input.tsx +++ b/18 Normalizr/src/common/components/Input.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import {ValidationFieldComponent} from './validationFieldComponent'; interface Props { name: string; @@ -10,37 +11,21 @@ interface Props { error?: string; } -export class Input extends React.Component { - constructor(props: Props) { - super(props); - } - - public render() { - let wrapperClass: string = "form-group"; - if (this.props.error && this.props.error.length > 0) { - wrapperClass += " " + "has-error"; - } - return ( -
- -
- -
- {this.props.error} -
-
-
- ); - } -} +export const Input = (props: Props) => { + return ( + + + + + ); +}; diff --git a/18 Normalizr/src/common/components/Select.tsx b/18 Normalizr/src/common/components/Select.tsx new file mode 100644 index 0000000..bf39c7a --- /dev/null +++ b/18 Normalizr/src/common/components/Select.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import {ValidationFieldComponent} from './validationFieldComponent'; + +interface Props { + name: string; + label: string; + onChange: any; + onBlur?: any; + value: any; + error?: string; + options: any; +} + +export const Select = (props: Props) => { + + return ( + + + + + + ); +} diff --git a/18 Normalizr/src/common/components/index.ts b/18 Normalizr/src/common/components/index.ts new file mode 100644 index 0000000..ebbe1ee --- /dev/null +++ b/18 Normalizr/src/common/components/index.ts @@ -0,0 +1,7 @@ +import {Input} from './Input'; +import {Select} from './Select'; + +export { + Input, + Select +} diff --git a/18 Normalizr/src/common/components/validationFieldComponent.tsx b/18 Normalizr/src/common/components/validationFieldComponent.tsx new file mode 100644 index 0000000..fc0be38 --- /dev/null +++ b/18 Normalizr/src/common/components/validationFieldComponent.tsx @@ -0,0 +1,26 @@ +import * as React from "react"; + +interface Props { + error?: string; +} + +export class ValidationFieldComponent extends React.Component { + constructor(props: Props) { + super(props); + } + + public render() { + let wrapperClass: string = "form-group"; + if (this.props.error && this.props.error.length > 0) { + wrapperClass += " has-error"; + } + return ( +
+ {this.props.children} +
+ {this.props.error} +
+
+ ); + } +} diff --git a/18 Normalizr/src/common/validations/validators.ts b/18 Normalizr/src/common/validations/required.ts similarity index 67% rename from 18 Normalizr/src/common/validations/validators.ts rename to 18 Normalizr/src/common/validations/required.ts index 232ee91..bb42dc8 100644 --- a/18 Normalizr/src/common/validations/validators.ts +++ b/18 Normalizr/src/common/validations/required.ts @@ -1,12 +1,12 @@ import { FieldValidationResult } from "lc-form-validation"; +import { validationsEnums } from "../validationsEnums"; -// TODO: Harcoded strings and Id's isolate them in a config class export const requiredValidationHandler = (vm: any, value: any): FieldValidationResult => { const isFieldInformed: boolean = (value != null && value.length > 0); - const errorInfo: string = (isFieldInformed) ? "" : "Mandatory field"; + const errorInfo: string = (isFieldInformed) ? "" : validationsEnums.REQUIRED.FIELD.MESSAGE; const fieldValidationResult: FieldValidationResult = new FieldValidationResult(); - fieldValidationResult.type = "REQUIRED"; + fieldValidationResult.type = validationsEnums.REQUIRED.FIELD.TYPE; fieldValidationResult.succeeded = isFieldInformed; fieldValidationResult.errorMessage = errorInfo; diff --git a/18 Normalizr/src/common/validations/requiredId.ts b/18 Normalizr/src/common/validations/requiredId.ts new file mode 100644 index 0000000..49010f0 --- /dev/null +++ b/18 Normalizr/src/common/validations/requiredId.ts @@ -0,0 +1,14 @@ +import { FieldValidationResult } from "lc-form-validation"; +import { validationsEnums } from "../validationsEnums"; + +export const requiredIdValidationHandler = (vm: any, value: {id: number}): FieldValidationResult => { + const isFieldInformed: boolean = (value != null && value.id && value.id > 0); + const errorInfo: string = (isFieldInformed) ? "" : validationsEnums.REQUIRED.FIELD.MESSAGE; + + const fieldValidationResult: FieldValidationResult = new FieldValidationResult(); + fieldValidationResult.type = validationsEnums.REQUIRED.FIELD.TYPE; + fieldValidationResult.succeeded = isFieldInformed; + fieldValidationResult.errorMessage = errorInfo; + + return fieldValidationResult; +}; diff --git a/18 Normalizr/src/common/validationsEnums.ts b/18 Normalizr/src/common/validationsEnums.ts index 6cd9b08..6a4a2f6 100644 --- a/18 Normalizr/src/common/validationsEnums.ts +++ b/18 Normalizr/src/common/validationsEnums.ts @@ -1,4 +1,10 @@ export const validationsEnums = { + REQUIRED: { + FIELD: { + TYPE: "REQUIRED_FIELD", + MESSAGE: "Mandatory field", + } + }, EMAIL: { NOT_VALID: { TYPE: "EMAIL_NOT_VALID", diff --git a/18 Normalizr/src/main.tsx b/18 Normalizr/src/main.tsx index 4069e1a..84e5a4f 100644 --- a/18 Normalizr/src/main.tsx +++ b/18 Normalizr/src/main.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import { Router, Route, IndexRoute, hashHistory } from "react-router"; import { syncHistoryWithStore} from "react-router-redux"; -import { createStore, applyMiddleware } from "redux"; +import { createStore, applyMiddleware, compose } from "redux"; import { Provider } from "react-redux"; import { reducers } from "./reducers"; import { App } from "./app"; @@ -13,7 +13,10 @@ import reduxThunk from "redux-thunk"; let store = createStore( reducers, - applyMiddleware(reduxThunk), + compose( + applyMiddleware(reduxThunk), + window['devToolsExtension'] ? window['devToolsExtension']() : f => f + ) ); const history = syncHistoryWithStore(hashHistory, store); diff --git a/18 Normalizr/src/model/api/country.ts b/18 Normalizr/src/model/api/country.ts new file mode 100644 index 0000000..3268dbb --- /dev/null +++ b/18 Normalizr/src/model/api/country.ts @@ -0,0 +1,9 @@ +export class Country { + id: number; + name: string; + + constructor() { + this.id = -1; + this.name = ""; + } +} diff --git a/18 Normalizr/src/model/api/student.ts b/18 Normalizr/src/model/api/student.ts index ff99025..0208db3 100644 --- a/18 Normalizr/src/model/api/student.ts +++ b/18 Normalizr/src/model/api/student.ts @@ -3,12 +3,14 @@ export class Student { gotActiveTraining: boolean; fullname: string; email: string; + countryId: number; public constructor() { this.id = -1; this.gotActiveTraining = false; this.fullname = ""; this.email = ""; + this.countryId = -1; } } diff --git a/18 Normalizr/src/model/mappers/countryMapper.ts b/18 Normalizr/src/model/mappers/countryMapper.ts new file mode 100644 index 0000000..2d0ca1e --- /dev/null +++ b/18 Normalizr/src/model/mappers/countryMapper.ts @@ -0,0 +1,18 @@ +import { Country } from '../api/country'; +import { CountryView } from '../view/countryView'; + +class CountryMapper { + mapCountryToCountryView(country: Country): CountryView { + return { + ...country + } + } + + mapCountryListToCountryViewList(countrys: Country[]): CountryView[] { + return countrys.map((country) => { + return this.mapCountryToCountryView(country); + }); + } +} + +export const countryMapper = new CountryMapper(); diff --git a/18 Normalizr/src/model/mappers/studentMapper.ts b/18 Normalizr/src/model/mappers/studentMapper.ts index 1e8226b..db1926e 100644 --- a/18 Normalizr/src/model/mappers/studentMapper.ts +++ b/18 Normalizr/src/model/mappers/studentMapper.ts @@ -7,13 +7,17 @@ class StudentMapper { id: student.id, gotActiveTraining: student.gotActiveTraining, fullname: student.fullname, - email: student.email - } + email: student.email, + country: { + id: student.countryId, + name: '', + }, + }; } mapStudentListToStudentViewList(students: Student[]): StudentView[] { return students.map((student) => { - return this.mapStudentToStudentView(student) + return this.mapStudentToStudentView(student); }); } } diff --git a/18 Normalizr/src/model/view/countryView.ts b/18 Normalizr/src/model/view/countryView.ts new file mode 100644 index 0000000..53e3784 --- /dev/null +++ b/18 Normalizr/src/model/view/countryView.ts @@ -0,0 +1,9 @@ +export class CountryView { + id: number; + name: string; + + constructor() { + this.id = -1; + this.name = ""; + } +} diff --git a/18 Normalizr/src/model/view/studentErrors.ts b/18 Normalizr/src/model/view/studentErrors.ts index f1a5ef0..5e9b888 100644 --- a/18 Normalizr/src/model/view/studentErrors.ts +++ b/18 Normalizr/src/model/view/studentErrors.ts @@ -3,4 +3,5 @@ import { FieldValidationResult } from "lc-form-validation"; export class StudentErrors { fullname: FieldValidationResult; email: FieldValidationResult; + country: FieldValidationResult; } diff --git a/18 Normalizr/src/model/view/studentView.ts b/18 Normalizr/src/model/view/studentView.ts index 426d1b4..212b1e7 100644 --- a/18 Normalizr/src/model/view/studentView.ts +++ b/18 Normalizr/src/model/view/studentView.ts @@ -1,13 +1,17 @@ +import { CountryView } from './countryView'; + export class StudentView { id: number; gotActiveTraining: boolean; fullname: string; email: string; + country: CountryView; public constructor() { this.id = -1; this.gotActiveTraining = false; this.fullname = ""; this.email = ""; + this.country = new CountryView(); } } diff --git a/18 Normalizr/src/pages/login/login.validation.ts b/18 Normalizr/src/pages/login/login.validation.ts index 0b1c48d..6ed6dd5 100644 --- a/18 Normalizr/src/pages/login/login.validation.ts +++ b/18 Normalizr/src/pages/login/login.validation.ts @@ -1,5 +1,5 @@ import { FieldValidationResult, BaseFormValidation } from "lc-form-validation"; -import { requiredValidationHandler } from "../../common/validations/validators"; +import { requiredValidationHandler } from "../../common/validations/required"; import { emailValidationHandler } from "../../common/validations/email"; class LoginFormValidation extends BaseFormValidation { diff --git a/18 Normalizr/src/pages/login/loginContainer.tsx b/18 Normalizr/src/pages/login/loginContainer.tsx index b78492f..9ec0310 100644 --- a/18 Normalizr/src/pages/login/loginContainer.tsx +++ b/18 Normalizr/src/pages/login/loginContainer.tsx @@ -3,8 +3,9 @@ import { LoginComponent } from './login'; import { LoginEntity } from '../../model/view/login'; import { updateEditingLogin } from './actions/updateEditingLogin'; import { loginRequestStartedAction} from './actions/loginRequestStarted'; +import {State} from '../../reducers'; -const mapStateToProps = (state) => { +const mapStateToProps = (state: State) => { return { loginInfo: state.sessionReducer.editingLogin } diff --git a/18 Normalizr/src/pages/student-detail/actions/addCountry.ts b/18 Normalizr/src/pages/student-detail/actions/addCountry.ts new file mode 100644 index 0000000..fff39d8 --- /dev/null +++ b/18 Normalizr/src/pages/student-detail/actions/addCountry.ts @@ -0,0 +1,11 @@ +import { actionsEnums } from "../../../common/actionsEnums"; +import {CountryView} from '../../../model/view/countryView'; +import {normalize} from 'normalizr'; +import {countrySchema} from '../../../schemas'; + +export const addCountryAction = (country: CountryView) => { + return { + type: actionsEnums.ADD_COUNTRY, + payload: normalize(country, countrySchema) + }; +}; diff --git a/18 Normalizr/src/pages/student-detail/actions/getStudentRequestStart.ts b/18 Normalizr/src/pages/student-detail/actions/getStudentRequestStart.ts index d3175ce..03386ca 100644 --- a/18 Normalizr/src/pages/student-detail/actions/getStudentRequestStart.ts +++ b/18 Normalizr/src/pages/student-detail/actions/getStudentRequestStart.ts @@ -1,8 +1,6 @@ import { actionsEnums } from "../../../common/actionsEnums"; import { studentApi } from "../../../rest-api/student-api"; import { getStudentRequestCompletedAction } from "./getStudentRequestCompleted"; -import { normalize } from 'normalizr' -import { studentSchema } from '../../../schema/schema' export const getStudentRequestStartAction = (studentId: number) => { return function(dispatcher) { @@ -10,11 +8,6 @@ export const getStudentRequestStartAction = (studentId: number) => { promise.then( data => { - console.log( - 'normalized response', - normalize(data, studentSchema) - ); - dispatcher(getStudentRequestCompletedAction(data)); } ); diff --git a/18 Normalizr/src/pages/student-detail/actions/studentFieldValueChangedStart.ts b/18 Normalizr/src/pages/student-detail/actions/studentFieldValueChangedStart.ts index 85713a7..1d2d3d6 100644 --- a/18 Normalizr/src/pages/student-detail/actions/studentFieldValueChangedStart.ts +++ b/18 Normalizr/src/pages/student-detail/actions/studentFieldValueChangedStart.ts @@ -1,11 +1,11 @@ import { studentFieldValueChangedCompleted } from "./studentFieldValueChangedCompleted"; import { FieldValidationResult } from "lc-form-validation"; -import { loginFormValidation} from "../../login/login.validation"; +import { studentFormValidation} from "../student.validation"; export function studentFieldValueChangedStart(viewModel: any, fieldName: string, value: any, event?: any) { return (dispatcher) => { - loginFormValidation.validateField(viewModel, fieldName, value, event).then( + studentFormValidation.validateField(viewModel, fieldName, value, event).then( (fieldValidationResult: FieldValidationResult) => dispatcher(studentFieldValueChangedCompleted(fieldName, value, fieldValidationResult )) ); }; diff --git a/18 Normalizr/src/pages/student-detail/components/studentForm.tsx b/18 Normalizr/src/pages/student-detail/components/studentForm.tsx index adad834..09a5754 100644 --- a/18 Normalizr/src/pages/student-detail/components/studentForm.tsx +++ b/18 Normalizr/src/pages/student-detail/components/studentForm.tsx @@ -1,13 +1,15 @@ -import * as React from "react"; -import { Input } from "../../../common/components/Input"; -import { StudentView } from "../../../model/view/studentView"; -import { StudentErrors } from "../../../model/view/studentErrors"; +import * as React from 'react'; +import { Input, Select } from '../../../common/components/'; +import { StudentView } from '../../../model/view/studentView'; +import { StudentErrors } from '../../../model/view/studentErrors'; +import { CountryView } from '../../../model/view/countryView'; interface Props { student: StudentView; errors: StudentErrors; fireFieldValueChanged: (viewModel: any, fieldName: string, value: any, filter?: any) => void; saveStudent: (student: StudentView) => void; + countries: CountryView[]; } export const StudentForm = (props: Props) => { @@ -24,6 +26,11 @@ export const StudentForm = (props: Props) => { props.saveStudent(props.student); }; + const onSelectCountry = (event, value) => { + let field = event.target.name; + + props.fireFieldValueChanged(props.student.country, field, value); + }; return (
@@ -31,20 +38,32 @@ export const StudentForm = (props: Props) => { +