<repo_name>kleap/iTechArt.Labs.iTechArtSurvey<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/reducers/surveyReducer.js
import * as types from './../actions/actionTypes';
import { questions } from './questionReducer';

export const surveyReducer = (state = {}, action) => {
    switch (action.type) {
        case types.SET_ITEMS_DESCRIPTION:
            return { ...state, itemDescriptions: action.items, fetching: false };
        case types.SET_FILTER_STRING:
            return { ...state, filter: action.input };

        case types.DELETE_ITEM:
            let index = state.itemDescriptions.findIndex(i => i.id === action.id);
            return {
                ...state,
                itemDescriptions: [
                    ...state.itemDescriptions.slice(0, index),
                    ...state.itemDescriptions.slice(index + 1)
                ]
            };
        case types.SET_ITEM:
            return { ...state, currentItem: action.item, fetching: false };
        case types.SET_MANAGE_MODE:
            return { ...state, manageMode: action.payload, fetching: true };
        case types.CLEAR_CURRENT_ITEM:
            return { ...state, currentItem: undefined };
        case types.SAVE_SURVEY_TITLE:
            return { ...state, currentItem: { ...state.currentItem, title: action.title } };

        case types.DELETE_QUESTION:
        case types.SAVE_QUESTION:
        case types.ADD_QUESTION:
            return {
                ...state,
                currentItem: {
                    ...state.currentItem,
                    questions: questions(state.currentItem.questions, action)
                }
            };
        default:
            return state;
    }
}

export default surveyReducer;
<file_sep>/iTechArt.Labs.iTechArtSurvey.BusinessLayer/Utils/SurveyDTOConverter.cs
﻿using System;
using System.Linq;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel;
using iTechArt.Labs.iTechArtSurvey.Web.Library.DTO;

namespace iTechArt.Labs.iTechArtSurvey.BusinessLayer.Utils
{
    internal static class SurveyDTOConverter
    {
        internal static SurveyDTO ConvertToDTO(this Survey survey)
        {
            var questions = survey.SurveyQuestions.Select(q => q.ConvertToDTO());
            var surveyDTO = new SurveyDTO()
            {
                Id = survey.Id,
                Title = survey.Title,
                Author = survey.Author == null ? "none" : survey.Author.Name,
                Questions = questions
            };

            return surveyDTO;
        }

        internal static Survey ConvertFromDTO(this SurveyDTO surveyDTO, User author)
        {
            var id = Guid.NewGuid();
            var survey = new Survey()
            {
                Id = id,
                Version = 0,
                Title = surveyDTO.Title,
                Author = author,
                Created = DateTime.Now,
                SurveyQuestions = surveyDTO
                .Questions
                .Select(q => q.ConvertFromDTO(id)).ToList()
            };

            return survey;
        }

        internal static Survey ConvertFromDTO(this SurveyDTO surveyDTO, User editor, Survey storedSurvey)
        {
            var survey = new Survey()
            {
                Id = storedSurvey.Id,
                Version = storedSurvey.Version + 1,
                Title = surveyDTO.Title,
                Author = storedSurvey.Author,
                Created = storedSurvey.Created,
                Edited = DateTime.Now,
                Editor = editor,
                SurveyQuestions = surveyDTO.Questions.Select(q => q.ConvertFromDTO(storedSurvey.Id)).ToList()
            };

            return survey;
        }

        internal static SurveyDescriptionDTO ConvertToDescriptionDTO(this Survey survey)
        {

            var surveyDescriptionDTO = new SurveyDescriptionDTO()
            {
                Id = survey.Id,
                Title = survey.Title,
                Author = survey.Author == null ? "none" : survey.Author.Name,
                QuestionsCount = survey.SurveyQuestions.Count,
                Description = $"Created at: {survey.Created.ToShortDateString()}"
            };

            return surveyDescriptionDTO;
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/Repository/IRepository.cs
﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.Repository
{
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {
        Task<IEnumerable<TEntity>> GetAllAsync();
        Task<TEntity> CreateAsync(TEntity entity);
        Task<TEntity> FindAsync(Guid id);
        Task<TEntity> FindAsync(Guid id, int version);
        Task<IEnumerable<TEntity>> FindAsync(Func<TEntity, bool> predicate);
        Task UpdateAsync(TEntity entity);
        Task DeleteAsync(TEntity entity);
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/App.js
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';

import Navigation from './components/navigation/Navigation';
import SurveyConstructorContainer from './scenes/newsurvey/SurveyConstructorContainer';
import SurveyManagerContainer from './scenes/mysurvey/SurveyManagerContainer';

class App extends Component {
  render() {
    return (
      <div className='row mt-5'>
        <Navigation />
        <main className='col-9 h-100'>
          <Switch>
            <Route path='/newsurvey' component={SurveyConstructorContainer} />
            <Route path='/mysurvey' component={SurveyManagerContainer} />
          </Switch>
        </main>
      </div>
    );
  }
}

export default App;
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/mysurvey/components/item-description/ItemDescription.js
import React, { Component } from 'react';
import { Button, Card, CardText, CardTitle, FormGroup } from 'reactstrap';
import ItemDescriptionSummary from './components/ItemDescriptionSummary';

class ItemDescription extends Component {
    deleteItem = () => {
        this.props.handleDeleteItem(this.props.item.id);
    };

    editItem = () => {
        this.props.handleEditItem(this.props.item.id);
    };

    render() {
        const { title, description, author, questionsCount } = this.props.item;
        return (
            <div className='col-md-6 col-lg-4 p-3'>
                <Card block >
                    <CardTitle className='text-truncate'>{title}</CardTitle>
                    <CardText className='text-justify'>
                        {description}
                    </CardText>
                    <ItemDescriptionSummary author={author} count={questionsCount} />
                    <FormGroup className='mt-3 d-flex justify-content-end'>
                        <Button onClick={this.editItem}>Edit</Button>
                        <Button onClick={this.deleteItem}>Delete</Button>
                    </FormGroup>
                </Card>
            </div>
        );
    }
}

export default ItemDescription;<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/components/question-editor/QuestionEditor.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Card, FormGroup, Label, Input, Form } from 'reactstrap';
import DeleteEditToolbar from './../delete-edit-toolbar/DeleteEditToolbar';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { deleteQuestion, saveQuestion } from './../../../../actions/actionCreators';
import { MultiQuestion, TextQuestion } from './../questions';


const mapStateToProps = (state) => ({
    state
});

const mapDispatchToProps = (dispatch) => ({
    deleteQuestion: bindActionCreators(deleteQuestion, dispatch),
    saveQuestion: bindActionCreators(saveQuestion, dispatch),
});

class QuestionEditor extends Component {
    constructor(props) {
        super(props);
        this.state = {
            editMode: false,
            question: props.question
        };
    }

    changeQuestionOption = (index, option) => {
        this.setState((prevState, props) => {
            return {
                question: {
                    ...prevState.question,
                    options: [
                        ...prevState.question.options.slice(0, index),
                        option,
                        ...prevState.question.options.slice(index + 1)
                    ]
                }
            };
        });
    }

    deleteQuestionOption = (index) => {
        this.setState((prevState, props) => {
            return {
                question: {
                    ...prevState.question,
                    options: [
                        ...prevState.question.options.slice(0, index),
                        ...prevState.question.options.slice(index + 1)
                    ]
                }
            };
        });
    }

    addQuestionOption = () => {
        this.setState((prevState, props) => {
            return {
                question: {
                    ...prevState.question,
                    options: [...prevState.question.options, '']
                }
            };
        });
    }

    getQuestionComponent(type) {
        switch (type) {
            case 'textarea':
                return <TextQuestion editMode={this.state.editMode} question={this.state.question} />;
            case 'checkbox':
            case 'radio':
                return <MultiQuestion type={type}
                    deleteOption={this.deleteQuestionOption}
                    changeOption={this.changeQuestionOption}
                    addOption={this.addQuestionOption}
                    editMode={this.state.editMode}
                    question={this.state.question} />
            default:
                break;

        }
    }

    setEditMode = (value) => {
        this.setState({
            editMode: value
        });
    }

    changeTitle = (e) => {
        const newTitle = e.target.value;
        this.setState((prevState, props) => {
            return {
                question: { ...prevState.question, title: newTitle }
            };
        });
    }

    saveQuestion = () => {
        this.props.saveQuestion(this.state.question);
    }
    
    render() {
        return (
            <div className='pt-3'>
                <Card block>
                    <div className='d-flex justify-content-end'>
                        <DeleteEditToolbar
                            id={this.props.question.id}
                            onSave={this.saveQuestion}
                            onDelete={this.props.deleteQuestion}
                            setEditMode={this.setEditMode}
                            editMode={this.state.editMode} />
                    </div>
                    <Form >
                        <FormGroup className='d-flex' >
                            <Label for='title' className='text-nowrap pr-2' >{this.props.index}{'.'}</Label>
                            <Input type='text'
                                disabled={!this.state.editMode}
                                value={this.state.question.title}
                                onChange={(e) => this.changeTitle(e)}
                                name='title' />
                        </FormGroup>
                        {this.getQuestionComponent(this.state.question.type)}
                    </Form>

                </Card>
            </div>
        );
    }
}

QuestionEditor.propTypes = {
    question: PropTypes.object.isRequired,
    index: PropTypes.number.isRequired
};

export default connect(mapStateToProps, mapDispatchToProps)(QuestionEditor);<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/components/loading/Loading.js
import React from 'react';
import PropTypes from 'prop-types';

const Loading = (props) => (
    props.loading &&
    <div className='d-flex justify-content-center m-5'>
        <i className='fa fa-spinner fa-spin fa-4x'></i>
    </div>
);

Loading.propTypes = {
    loading: PropTypes.bool
};

Loading.defaultProps = {
    loading: true
};
export default Loading;<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/components/question-manager/QuestionManager.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import QuestionEditor from './../question-editor/QuestionEditor';

class QuestionManager extends Component {
    render() {
        const questions = this.props.questions.map((q, index) => (
           <QuestionEditor key={q.id}
                question={q}
                index={index + 1} />
        ));
        return (
            <div className='mt-3'>
                {questions}
            </div>
        );
    }
}

QuestionManager.propTypes = {
    questions: PropTypes.array
};

export default QuestionManager;<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/mysurvey/components/item-description/components/ItemDescriptionSummary.js
import React from 'react';
import PropTypes from 'prop-types';

const ItemDescriptionSummary = (props) => (
    <p className='card-text'>
        <span className='font-italic'>{props.author}</span>
        {', '}
        <span><b>{props.count} </b>
            {' questions.'}
        </span>
    </p>
);

ItemDescriptionSummary.propTypes = {
    count: PropTypes.number,
    author: PropTypes.string
};

ItemDescriptionSummary.defaultProps = {
    count: 0,
    author: 'none'
};

export default ItemDescriptionSummary;

<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/SurveyConstructorContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addQuestion, saveSurveyTitle } from './../../actions/actionCreators';
import { saveItem, editItem, cancelCreation } from './../../actions/middlewareActionCreators';

import SurveyConstructor from './components/survey-constructor/SurveyConstructor';

const mapStateToProps = (state) => ({
    item: state.surveys.currentItem,
    manageMode: state.surveys.manageMode,
    fetching: state.surveys.fetching
});

const mapDispatchToProps = (dispatch) => ({
    cancelCreation: bindActionCreators(cancelCreation, dispatch),
    saveItem: bindActionCreators(saveItem, dispatch),
    editSurvey: bindActionCreators(editItem, dispatch),
    addQuestion: bindActionCreators(addQuestion, dispatch),
    saveTitle: bindActionCreators(saveSurveyTitle, dispatch)
});

class SurveyConstructorContainer extends Component {
    render() {
        return (
            <SurveyConstructor
                info={{
                    title: this.props.item.title,
                    id: this.props.item.id,
                    author: this.props.item.author
                }}
                fetching={this.props.fetching}
                questions={this.props.item.questions}
                manageMode={this.props.manageMode}
                cancelCreation={this.props.cancelCreation}
                addQuestion={this.props.addQuestion}
                saveTitle={this.props.saveTitle}
                saveItem={this.props.saveItem}
            />
        );
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SurveyConstructorContainer);<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/Repository/GenericRepository.cs
﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.Repository
{
    public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly DbContext _context;
        private readonly DbSet<TEntity> _dbSet;
        private bool disposed = false;

        public GenericRepository(DbContext context)
        {
            _context = context;
            _dbSet = _context.Set<TEntity>();
        }

        public async Task<TEntity> CreateAsync(TEntity entity)
        {
            _dbSet.Add(entity);
            await _context.SaveChangesAsync();
            return entity;
        }

        public async Task DeleteAsync(TEntity entity)
        {
            _dbSet.Remove(entity);
            await _context.SaveChangesAsync();
        }


        public async Task<IEnumerable<TEntity>> FindAsync(Func<TEntity, bool> predicate)
        {
            var resultSet = await _dbSet.ToListAsync();
            return resultSet.Where(predicate);
        }

        public Task<TEntity> FindAsync(Guid id)
        {
            return _dbSet.FindAsync(id);
        }

        public Task<TEntity> FindAsync(Guid id, int version)
        {
            return _dbSet.FindAsync(id, version);
        }

        public async Task<IEnumerable<TEntity>> GetAllAsync()
        {
            return await _dbSet.AsNoTracking().ToListAsync();
        }

        public async Task UpdateAsync(TEntity entity)
        {
            _dbSet.Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
            await _context.SaveChangesAsync();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                _context.Dispose();
            }

            disposed = true;
        }

    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/components/delete-edit-toolbar/DeleteEditToolbar.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

class DeleteEditToolbar extends Component {

    onSave = () => {
        if (this.props.editMode) {
            this.props.onSave();
        }
        this.props.setEditMode(!this.props.editMode)
    }

    render() {
        return (
            <div className={classNames({
                'invisible': this.props.hidden
            })}>
                <a role='button'
                    onClick={() => this.onSave()}
                    className={classNames({
                        'fa p-1': true,
                        'fa-pencil': !this.props.editMode,
                        'fa-save': this.props.editMode,
                        'invisible': this.props.deleteOnly
                    })}>{''}</a>

                <a role='button'
                    className='fa fa-trash ml-3'
                    onClick={() => this.props.onDelete(this.props.id)} >{''}</a>
            </div>
        );
    }
}

DeleteEditToolbar.propTypes = {
    onDelete: PropTypes.func.isRequired,
    hidden: PropTypes.bool,
    editMode: PropTypes.bool
};

DeleteEditToolbar.defaultProps = {
    editMode: false
}
export default DeleteEditToolbar;<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/reducers/questionReducer.js
import * as types from './../actions/actionTypes';

export const questions = (questions = [], action) => {
    switch (action.type) {
        case types.SAVE_QUESTION:
            return questions.map(q => {
                return q.id === action.question.id ?
                    action.question : q;
            });
        case types.DELETE_QUESTION:
            let index = questions.findIndex(q => q.id === action.questionId);
            return [
                ...questions.slice(0, index),
                ...questions.slice(index + 1),
            ];
        case types.ADD_QUESTION:
            return [...questions, action.question];
        default:
            return questions;
    }
}<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/EF/EntityConfigurations/SurveyQuestionConfiguration.cs
﻿using System.Data.Entity.ModelConfiguration;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.EF.EntityConfigurations
{
    class SurveyQuestionConfiguration : EntityTypeConfiguration<SurveyQuestion>
    {
        public SurveyQuestionConfiguration()
        {
            HasKey(s => s.Id)
                .Property(s => s.Id)
                .HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);

            HasRequired(s => s.Survey)
                .WithMany(s => s.SurveyQuestions)
                .HasForeignKey(q => new { q.SurveyId, q.SurveyVersion }).WillCascadeOnDelete(true);
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/mysurvey/components/item-manager/ItemManager.js
import React, { Component } from 'react';
import { Row } from 'reactstrap';
import PropTypes from 'prop-types';

import ItemDescription from './../item-description/ItemDescription';
import Loading from './../../../../components/loading/Loading';
import SearchToolbar from './../search-toolbar/SearchToolbar';

class ItemManager extends Component {
    componentDidMount() {
        this.props.getItemDescriptions()
    }
    render() {
        const items = this.props.items.map((item) => (
            <ItemDescription key={item.id}
                item={item}
                handleEditItem={this.props.handleEditItem}
                handleDeleteItem={this.props.handleDeleteItem} />
        ));

        if (this.props.fetching) {
            return <Loading loading={this.props.fetching} />;
        } else {
            return (
                <div className='h-100'>
                    <SearchToolbar
                        header="Surveys"
                        link="New"
                        linkValue="/newsurvey"
                        handleSearch={this.props.handleSearch}
                        filterString={this.props.filterString}/>
                    <Row>
                        {items}
                    </Row>
                    <h4>{'Count: ' + this.props.totalCount}</h4>
                </div>
            );
        }
    }
}

ItemManager.propTypes = {
    items: PropTypes.array,
    fetching: PropTypes.bool.isRequired,
    totalCount: PropTypes.number,
    filterString: PropTypes.string.isRequired,
    getItemDescriptions: PropTypes.func.isRequired,
    handleSearch: PropTypes.func.isRequired,
    handleDeleteItem: PropTypes.func.isRequired,
    handleEditItem: PropTypes.func.isRequired,
};

ItemManager.defaultProps = {
    items: []
};

export default ItemManager;<file_sep>/iTechArt.Labs.iTechArtSurvey.BusinessLayer/Utils/ObjectWithMaxPropertyLinqExtension.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace iTechArt.Labs.iTechArtSurvey.BusinessLayer.Utils
{
    internal static class ObjectWithMaxPropertyLinqExtension
    {
        internal static T GetByMax<T>(this IEnumerable<T> collection, Func<T, int> selector)
        {
            return collection.OrderByDescending(selector).FirstOrDefault();
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web.Library/DTO/SurveyDescriptionDTO.cs
﻿using System;
namespace iTechArt.Labs.iTechArtSurvey.Web.Library.DTO
{
    public class SurveyDescriptionDTO
    {
        public Guid Id { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public string Description { get; set; }
        public int QuestionsCount { get; set; }
    }
}<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/actions/middlewareActionCreators.js
import { deleteItem, setItemDescriptions, setManageMode, setItem } from './actionCreators';
import * as api from './../api/apiCalls';
import { push, goBack } from 'react-router-redux';

export const deleteItemFromServer = (id) => (dispatch) => {
    return api.deleteItem(id)
        .then((response) => {
            if (!response.data.isError) {
                dispatch(deleteItem(id));
            } else {
                return "error";
            }
        })
        .catch((error) => {
            return "error";
        });
}
export const getItemDescriptionsFromServer = () => (dispatch) => {
    return api.getDescriptions()
        .then((response) => {
            dispatch(setItemDescriptions(response.data.data));
        })
        .catch((error) => {
            return "error";
        });
}

export const editItem = (id) => (dispatch) => {
    return api.getItem(id)
        .then((response) => {
            dispatch(setManageMode(true));
            dispatch(push('/newsurvey'));
            dispatch(setItem(response.data.data));
        })
        .catch((error) => {
            console.log(error);
        });
}

export const saveItem = (item) => (dispatch) => {
    console.log(item);
    return api.saveItem(item)
        .then((response) => {
            dispatch(setManageMode(false));
            dispatch(push('/mysurvey'));
            dispatch(setItem({}));
        });
}

export const cancelCreation = (item) => (dispatch) => {
    dispatch(goBack());
    return dispatch(setManageMode(false));
}<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/actions/actionTypes.js
export const SET_FILTER_STRING = 'SET_FILTER_STRING';
export const DELETE_ITEM = 'DELETE_ITEM';
export const SET_ITEM = 'SET_ITEM';
export const SAVE_SURVEY_TITLE = 'SAVE_SURVEY_TITLE';
export const SET_MANAGE_MODE = 'SET_MANAGE_MODE';
export const SET_ITEMS_DESCRIPTION = 'SET_ITEMS_DESCRIPTION';
export const CLEAR_CURRENT_ITEM = 'CLEAR_CURRENT_ITEM';
export const SAVE_QUESTION = 'SAVE_QUESTION';
export const DELETE_QUESTION = 'DELETE_QUESTION';
export const ADD_QUESTION = 'ADD_QUESTION';

<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/actions/actionCreators.js
import * as types from './actionTypes';

export function deleteItem(id) {
    return {
        type: types.DELETE_ITEM,
        id
    };
}

export function setFilter(input) {
    return {
        type: types.SET_FILTER_STRING,
        input
    };
}

export function saveSurveyTitle(title) {
    return {
        type: types.SAVE_SURVEY_TITLE,
        title
    }
}

export function setItemDescriptions(items) {
    return {
        type: types.SET_ITEMS_DESCRIPTION,
        items
    };
}

export function setItem(item) {
    return {
        type: types.SET_ITEM,
        item
    };
}

export function setManageMode(payload) {
    return {
        type: types.SET_MANAGE_MODE,
        payload
    };
}

export function clearCurrentItem() {
    return {
        type: types.CLEAR_CURRENT_ITEM
    };
}

export const addQuestion = (question) => {
    return {
        type: types.ADD_QUESTION,
        question
    }
}

export function saveQuestion(question) {
    return {
        type: types.SAVE_QUESTION,
        question
    }
}

export function deleteQuestion(questionId) {
    return {
        type: types.DELETE_QUESTION,
        questionId
    }
}

<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/configStore.js
import rootReducer from './rootReducer';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const initialState = {
    surveys: {
        fetching: true,
        manageMode: false,
        itemDescriptions: [],
        filter: '',
        currentItem: {}
    }
};

const configStore = (middleware) => (createStore(
    rootReducer,
    initialState,
    compose(
        applyMiddleware(thunk, ...middleware),
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    ))
);

export default configStore;<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/DomainModel/Survey.cs
﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel
{
    public class Survey
    {
        public Guid Id { get; set; }
        public int Version { get; set; }
        [MinLength(1, ErrorMessage = "Survey title must be not empty")]
        public string Title { get; set; }
        public virtual User Author { get; set; }
        public DateTime Created { get; set; }
        public virtual User Editor { get; set; }
        public DateTime? Edited { get; set; }
        public virtual ICollection<SurveyQuestion> SurveyQuestions { get; set; }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/Controllers/SurveysController.cs
﻿using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Cors;
using iTechArt.Labs.iTechArtSurvey.BusinessLayer.SurveyManagement;
using iTechArt.Labs.iTechArtSurvey.Web.Library;
using iTechArt.Labs.iTechArtSurvey.Web.Library.DTO;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;

namespace iTechArt.Labs.iTechArtSurvey.Web.Controllers
{

    [RoutePrefix("surveys")]
    [EnableCors(origins: "http://localhost:3000", headers: "*", methods: "*")]
    public class SurveysController : ApiController
    {
        private SurveyManager _manager;

        public SurveyManager Manager
        {
            get
            {
                return _manager ?? Request.GetOwinContext().Get<SurveyManager>();
            }
        }

        // GET: api/surveys
        [HttpGet]
        public Task<ServerResponse> GetSurveyDescriptions()
        {
            return Manager.GetSurveyDescriptionsAsync();
        }

        //POST apt/surveys/
        [HttpPost]
        public Task<ServerResponse> SaveSurvey([FromBody]SurveyDTO survey)
        {
            var userId = User.Identity.GetUserId();
            return Manager.Save(userId, survey);
        }

        // GET: api/surveys/5
        public Task<ServerResponse> Get(Guid id)
        {
            return Manager.GetAsync(id);
        }



        // DELETE: api/Surveys/5
        public Task<ServerResponse> Delete(Guid id)
        {
            return Manager.Delete(id);
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/mysurvey/SurveyManagerContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ItemManager from './components/item-manager/ItemManager';
import { setFilter } from './../../actions/actionCreators';

import { getItemDescriptionsFromServer, deleteItemFromServer, editItem } from './../../actions/middlewareActionCreators';

import { getFilteredItems } from './selectors/selector';

const mapStateToProps = (state, props) => ({
    surveys: getFilteredItems()(state, props),
    fetching: state.surveys.fetching,
    totalCount: state.surveys.itemDescriptions.length,
    filterString: state.surveys.filter
});

const mapDispatchToProps = (dispatch) => ({
    getItemDescriptions: bindActionCreators(getItemDescriptionsFromServer, dispatch),
    search: bindActionCreators(setFilter, dispatch),
    deleteItem: bindActionCreators(deleteItemFromServer, dispatch),
    editItem: bindActionCreators(editItem, dispatch)
});

class SurveyManagerContainer extends Component {
    render() {
        return (
            <ItemManager
                filterString={this.props.filterString}
                fetching={this.props.fetching}
                totalCount={this.props.totalCount}
                items={this.props.surveys}

                getItemDescriptions={this.props.getItemDescriptions}
                handleSearch={this.props.search}
                handleDeleteItem={this.props.deleteItem}
                handleEditItem={this.props.editItem}
            />
        );
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SurveyManagerContainer);<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/api/apiCalls.js
import axios from 'axios';

export const getDescriptions = () => {
    return axios.get('http://localhost:3179/api/surveys/');
}

export const getItem = (id) => {
    return axios.get('http://localhost:3179/api/surveys/' + id);
}

export const saveItem = (data) => {
    return axios.post('http://localhost:3179/api/surveys/', data);
}
export const deleteItem = (id)=>{
    return axios.delete('http://localhost:3179/api/surveys/' + id);
}<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/EF/EntityConfigurations/QuestionConfiguration.cs
﻿using System.Data.Entity.ModelConfiguration;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.EF.EntityConfigurations
{
    internal class QuestionConfiguration : EntityTypeConfiguration<Question>
    {
        public QuestionConfiguration()
        {
            HasKey(q => q.Id);
            HasOptional(q => q.Replies);
            HasRequired(q => q.SurveyQuestion)
               .WithOptional(s => s.Question)
               .WillCascadeOnDelete(true);

            Property(q => q.Required).IsRequired();
            Property(q => q.Title)
                .IsRequired()
                .HasMaxLength(256);
            Property(q => q.Type).IsRequired();
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.BusinessLayer/SurveyManagement/SurveyManager.cs
﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Linq;
using System.Threading.Tasks;
using iTechArt.Labs.iTechArtSurvey.BusinessLayer.UserManagement;
using iTechArt.Labs.iTechArtSurvey.BusinessLayer.Utils;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.EF;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.Repository;
using iTechArt.Labs.iTechArtSurvey.Web.Library;
using iTechArt.Labs.iTechArtSurvey.Web.Library.DTO;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;

namespace iTechArt.Labs.iTechArtSurvey.BusinessLayer.SurveyManagement
{
    public class SurveyManager : IDisposable
    {
        private bool disposed = false;
        private readonly IRepository<Survey> _repository;
        private SurveyUserManager _userManager;

        public SurveyManager(IRepository<Survey> repository, SurveyUserManager userManager)
        {
            _repository = repository;
            _userManager = userManager;
        }

        public async Task<ServerResponse> GetSurveyDescriptionsAsync()
        {
            var storedSurveys = await _repository.GetAllAsync();
            var latestSurveys = GetLatestVersionsOfSurveys(storedSurveys);
            var surveysDTO = latestSurveys
                .Select(s => s.ConvertToDescriptionDTO()).ToList();

            return new ServerResponse()
            {
                IsError = false,
                Message = "latest surveys",
                Data = surveysDTO
            };
        }

        public async Task<ServerResponse> Delete(Guid id)
        {
            bool isError = false;
            try
            {
                var storedSurveys = await _repository.FindAsync(s => s.Id == id);

                for (int i = 0; i < storedSurveys.Count(); i++)
                {
                    await _repository.DeleteAsync(storedSurveys.ElementAt(i));
                }
            }
            catch (Exception)
            {
                isError = true;
                throw;
            }

            return new ServerResponse()
            {
                IsError = isError
            };

        }

        private IEnumerable<Survey> GetLatestVersionsOfSurveys(IEnumerable<Survey> surveys)
        {
            return surveys
                .GroupBy(s => s.Id)
                .Select(g => g.Select(s => s).GetByMax(s => s.Version));
        }

        public async Task<ServerResponse> GetAsync(Guid id)
        {
            var surveys = (await _repository.FindAsync(s => s.Id == id));
            var latestSurvey = surveys.GetByMax(s => s.Version);
            var latestSurveyDTO = latestSurvey.ConvertToDTO();

            return new ServerResponse()
            {
                IsError = false,
                Message = "OK",
                Data = latestSurveyDTO
            };
        }

        public async Task<ServerResponse> Save(string userId, SurveyDTO survey)
        {
            var user = await _userManager.FindByIdAsync(userId);
            ServerResponse response = null;
            string message = string.Empty;
            bool isError = false;
            try
            {
                var alreadyInDatabaseSurvey = (await _repository
                    .FindAsync(s => s.Id == survey.Id)).GetByMax(s => s.Version);

                if (alreadyInDatabaseSurvey != null)
                {
                    var modifiedSurvey = survey.ConvertFromDTO(user, alreadyInDatabaseSurvey);
                    await _repository.CreateAsync(modifiedSurvey);
                }
                else
                {
                    var newSurvey = survey.ConvertFromDTO(user);
                    await _repository.CreateAsync(newSurvey);
                }
                message = "Survey was successfully saved";

            }
            catch (ArgumentException)
            {
                isError = true;
                message = "Error occured while saving survey, unknown type of question.";
            }
            catch (DbEntityValidationException e)
            {
                isError = true;
                message = "Error occured while saving survey: " + GetErrorsMessages(e.EntityValidationErrors);
            }
            finally
            {
                response = new ServerResponse()
                {
                    IsError = isError,
                    Message = message
                };
            }

            return response;
        }

        private string GetErrorsMessages(IEnumerable<DbEntityValidationResult> results)
        {
            string message = "\n";

            foreach (var result in results)
            {
                foreach (var error in result.ValidationErrors)
                {
                    message += error.ErrorMessage + "\n";
                }
            }

            return message;
        }

        public static SurveyManager Create(
          IdentityFactoryOptions<SurveyManager> identityFactoryOptions,
          IOwinContext context)
        {
            return new SurveyManager(
                new GenericRepository<Survey>(context.Get<SurveyContext>()),
                context.Get<SurveyUserManager>());
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                _repository.Dispose();
                _userManager.Dispose();
            }

            disposed = true;
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web.Library/DTO/QuestionDTO.cs
﻿using System;
using System.Collections.Generic;
namespace iTechArt.Labs.iTechArtSurvey.Web.Library.DTO
{
    public class QuestionDTO
    {
        public Guid Id { get; set; }
        public string Title { get; set; }
        public int Order { get; set; }
        public string Type { get; set; }
        public IEnumerable<string> Options { get; set; }
    }
}<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/mysurvey/components/search-toolbar/SearchToolbar.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Input, Button } from 'reactstrap';

class SearchToolbar extends Component {
    handleSearch(e) {
        this.props.handleSearch(e.target.value);
    }

    render() {
        return (
            <div className='d-flex justify-content-between'>
                <div className='d-flex'>
                    <h3>{this.props.header}</h3>
                    <Link to={this.props.linkValue}>
                        <Button color='secondary' className='ml-4'>{this.props.link}</Button>
                    </Link>
                </div>
                <Input className='w-25'
                    type='search'
                    value={this.props.filterString}
                    placeholder='search...'
                    onChange={(e)=>this.handleSearch(e)} />
            </div>
        );
    }
}
SearchToolbar.propTypes = {
    header: PropTypes.string,
    link: PropTypes.string.isRequired,
    linkValue: PropTypes.string.isRequired,
    handleSearch: PropTypes.func.isRequired,
    filterString: PropTypes.string
};

SearchToolbar.defaultProps = {
    filterString: ""
};
export default SearchToolbar;<file_sep>/iTechArt.Labs.iTechArtSurvey.BusinessLayer/Config/OwinConfiguration.cs
﻿using iTechArt.Labs.iTechArtSurvey.BusinessLayer.SurveyManagement;
using iTechArt.Labs.iTechArtSurvey.BusinessLayer.UserManagement;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.EF;
using Owin;

namespace iTechArt.Labs.iTechArtSurvey.BusinessLayer.Config
{
    public static class OwinConfiguration
    {
        public static void RegisterManagers(IAppBuilder app)
        {
            app.CreatePerOwinContext(SurveyContext.Create);
            app.CreatePerOwinContext<SurveyUserManager>(SurveyUserManager.Create);
            app.CreatePerOwinContext<SignInManager>(SignInManager.Create);
            app.CreatePerOwinContext<SurveyRoleManager>(SurveyRoleManager.Create);
            app.CreatePerOwinContext<SurveyManager>(SurveyManager.Create);
        }

    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web.Library/DTO/SurveyDTO.cs
﻿using System;
using System.Collections.Generic;

namespace iTechArt.Labs.iTechArtSurvey.Web.Library.DTO
{
    public class SurveyDTO
    {
        public Guid Id { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public IEnumerable<QuestionDTO> Questions { get; set; }
    }
}<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/DomainModel/SurveyQuestion.cs
﻿using System;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel
{
    public class SurveyQuestion
    {
        public Guid Id { get; set; }
        public int Order { get; set; }
        public Guid SurveyId { get; set; }
        public int SurveyVersion { get; set; }
        public virtual Survey Survey { get; set; }
        public virtual Question Question { get; set; }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/components/survey-constructor/SurveyConstructor.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ButtonGroup, ButtonToolbar, Col, Button, FormGroup, Label, Input, FormText } from 'reactstrap';
import QuestionPicker from './../question-picker/QuestionPicker';
import QuestionManager from './../question-manager/QuestionManager';
import * as api from './../../../../api/apiCalls';
import Loading from './../../../../components/loading/Loading';

class SurveyConstructor extends Component {
    leaveWithoutSaving = () => {
        this.props.cancelCreation();
    }

    setTitle = (e) => {
        this.props.saveTitle(e.target.value);
    }

    saveItem = () => {
        this.props.saveItem({
            ...this.props.info,
            questions: [...this.props.questions]
        });
    }

    render() {
        if (this.props.manageMode && this.props.fetching) {
            return <Loading loading={this.props.fetching} />;
        } else {
            return (
                <div className='row mt-2'>
                    <Col>
                        <FormGroup row>
                            <Label for='title' className='text-nowrap' sm={2}>Title</Label>
                            <Col sm={10}>
                                <Input type='text'
                                    defaultValue={this.props.info.title}
                                    onBlur={this.setTitle}
                                    name='title' />
                            </Col>
                        </FormGroup>
                        <FormText color='muted'>
                            Questions: {this.props.questions.length},<i className='pl-2'>Author</i> : {this.props.author}
                        </FormText>
                        <ButtonToolbar className='d-flex justify-content-end mt-4'>
                            <ButtonGroup>
                                <Button onClick={this.saveItem}>Save</Button>
                                <Button onClick={this.leaveWithoutSaving}>Cancel</Button>
                            </ButtonGroup>
                        </ButtonToolbar>
                        <QuestionManager
                            questions={this.props.questions}
                        />
                    </Col>
                    <div>
                        <QuestionPicker
                            addQuestion={this.props.addQuestion} />
                    </div>

                </div>
            );
        }
    }
}
SurveyConstructor.propTypes = {
    info: PropTypes.object,
    questions: PropTypes.array.isRequired,
    manageMode: PropTypes.bool,
    fetching: PropTypes.bool,
    addQuestion: PropTypes.func.isRequired,
    saveItem: PropTypes.func.isRequired,
    saveTitle: PropTypes.func.isRequired,
    cancelCreation: PropTypes.func.isRequired,
};

SurveyConstructor.defaultProps = {
    title: 'Default survey',
    id: "0000000-0000-0000-0000-000000000000",
    author: "new",
    questions: [
        {
            "id": 0,
            "title": "Вопрос с текстовым вариантом ответа",
            "type": "textarea",
            "required": true
        },
        {
            "id": 1,
            "title": "Вопрос с множеством вариантов ответа",
            "type": "checkbox",
            "required": true,
            "options": [
                "Первый вариант",
                "Второй вариант",
                "Третий вариант",
                "Четвертый вариант"
            ]
        },
        {
            "id": 2,
            "title": "Вопрос с выбором одного варианта ответа",
            "type": "radio",
            "required": true,
            "options": [
                "Первый вариант",
                "Второй вариант",
                "Третий вариант",
                "Четвертый вариант"
            ]
        },
    ]
};


export default SurveyConstructor;<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/src/surveys/scenes/newsurvey/components/questions/TextQuestion.js
import React, { Component } from 'react';
import { FormGroup, Label, Input } from 'reactstrap';

class TextQuestion extends Component {
    render() {
        return (
            <FormGroup>
                <Label for="exampleText">Reply</Label>
                <Input type="textarea" disabled name="text" id="exampleText" />
            </FormGroup>
        );
    }
}
export default TextQuestion;<file_sep>/iTechArt.Labs.iTechArtSurvey.BusinessLayer/Utils/QuestionDTOConverter.cs
﻿using System;
using System.Collections.Generic;
using iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel;
using iTechArt.Labs.iTechArtSurvey.Web.Library.DTO;
using Newtonsoft.Json;

namespace iTechArt.Labs.iTechArtSurvey.BusinessLayer.Utils
{
    internal static class QuestionDTOConverter
    {
        internal static QuestionDTO ConvertToDTO(this SurveyQuestion surveyQuestion)
        {
            var questionDTO = new QuestionDTO()
            {
                Id = surveyQuestion.Question.Id,
                Order = surveyQuestion.Order,
                Title = surveyQuestion.Question.Title,
                Type = surveyQuestion.Question.Type.ToString(),
                Options = JsonConvert.DeserializeObject<List<string>>(surveyQuestion.Question.JsonMetaInformation)
            };

            return questionDTO;
        }

        internal static SurveyQuestion ConvertFromDTO(this QuestionDTO questionDTO, Guid surveyId)
        {
            var surveyQuestion = new SurveyQuestion()
            {
                Order = questionDTO.Order,
                SurveyId = surveyId,
                Question = new Question()
                {
                    Title = questionDTO.Title,
                    Required = true,
                    Type = (QuestionType)Enum.Parse(typeof(QuestionType), questionDTO.Type),
                    JsonMetaInformation = JsonConvert.SerializeObject(questionDTO.Options),
                }
            };

            return surveyQuestion;
        }
    }
}
<file_sep>/iTechArt.Labs.iTechArtSurvey.DataAccessLayer/DomainModel/Question.cs
﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace iTechArt.Labs.iTechArtSurvey.DataAccessLayer.DomainModel
{
    public class Question
    {
        public Guid Id { get; set; }
        [MinLength(1, ErrorMessage = "Question title must be not empty")]
        public string Title { get; set; }
        public QuestionType Type { get; set; }
        public bool Required { get; set; }
        public string JsonMetaInformation { get; set; }
        public virtual SurveyQuestion SurveyQuestion { get; set; }
        public virtual ICollection<Reply> Replies { get; set; }
    }
}<file_sep>/iTechArt.Labs.iTechArtSurvey.Web/Startup.cs
﻿using System.Web.Http;
using iTechArt.Labs.iTechArtSurvey.BusinessLayer.Config;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Newtonsoft.Json.Serialization;
using Owin;

[assembly: OwinStartup(typeof(iTechArt.Labs.iTechArtSurvey.Web.Startup))]
namespace iTechArt.Labs.iTechArtSurvey.Web
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            OwinConfiguration.RegisterManagers(app);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            var config = new HttpConfiguration();
            config.EnableCors();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;

            app.UseWebApi(config);
        }
    }
}
