Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6. Feature/removing slide from training modules #5916

Open
wants to merge 8 commits into
base: Om-Training
Choose a base branch
from
114 changes: 96 additions & 18 deletions app/assets/javascripts/actions/training_modification_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ import request from '../utils/request';
import { setInvalid } from './validation_actions';
import { SET_TRAINING_MODE } from '../constants/training';

// For Modifying Training Content
const libraryValidationRules = [
{ keyword: 'name', field: 'name' },
{ keyword: 'slug', field: 'slug' },
{ keyword: 'introduction', field: 'introduction' }
];

const categoryValidationRules = [
{ keyword: 'title', field: 'title' },
{ keyword: 'description', field: 'description' }
];

const moduleValidationRules = [
{ keyword: 'name', field: 'name' },
{ keyword: 'slug', field: 'slug' },
{ keyword: 'description', field: 'description' }
];

const slideValidationRules = [
{ keyword: 'title', field: 'title' },
{ keyword: 'slug', field: 'slug' },
{ keyword: 'wikipage', field: 'wiki_page' }
];

// For switching between edit and view mode
export const getTrainingMode = () => (dispatch) => {
dispatch({
Expand Down Expand Up @@ -36,24 +60,6 @@ export const updateTrainingMode = (editMode, setUpdatingEditMode) => (dispatch)
.catch(data => dispatch({ type: API_FAIL, data }));
};

// For Modifying Training Content
const libraryValidationRules = [
{ keyword: 'name', field: 'name' },
{ keyword: 'slug', field: 'slug' },
{ keyword: 'introduction', field: 'introduction' }
];

const categoryValidationRules = [
{ keyword: 'title', field: 'title' },
{ keyword: 'description', field: 'description' }
];

const moduleValidationRules = [
{ keyword: 'name', field: 'name' },
{ keyword: 'slug', field: 'slug' },
{ keyword: 'description', field: 'description' }
];

const performValidation = (error, dispatch, validationRules) => {
const errorMessages = error.responseText.errorMessages;
let apiFailDispatched = false;
Expand All @@ -76,6 +82,7 @@ const performValidation = (error, dispatch, validationRules) => {
};


// For Creating New Library
const createLibraryPromise = async (library, setSubmitting) => {
const response = await request('/training/create_library', {
method: 'POST',
Expand Down Expand Up @@ -110,6 +117,7 @@ export const createLibrary = (library, setSubmitting, toggleModal) => (dispatch)
});
};

// For Creating New Category
const createCategoryPromise = async (library_id, category, setSubmitting) => {
const response = await request(`/training/${library_id}/create_category`, {
method: 'POST',
Expand Down Expand Up @@ -141,6 +149,7 @@ export const createCategory = (library_id, category, setSubmitting, toggleModal)
});
};

// For Adding New Module
const addModulePromise = async (library_id, category_id, module, setSubmitting) => {
const response = await request(`/training/${library_id}/${category_id}/add_module`, {
method: 'POST',
Expand Down Expand Up @@ -168,3 +177,72 @@ export const addModule = (library_id, category_id, module, setSubmitting) => (di
performValidation(error, dispatch, moduleValidationRules);
});
};

// For Transferring Modules
const transferModulesPromise = async (library_id, transferInfo, setSubmitting) => {
const response = await request(`/training/${library_id}/transfer_modules`, {
method: 'POST',
body: JSON.stringify({ transferInfo }),
});
setSubmitting(false);
if (!response.ok) {
logErrorMessage(response);
const data = await response.json();
response.responseText = data;
throw response;
}
return response.json();
};

export const transferModules = (library_id, transferInfo, setSubmitting) => (dispatch) => {
return transferModulesPromise(library_id, transferInfo, setSubmitting)
.then(() => {
window.location.reload();
})
.catch(data => dispatch({ type: API_FAIL, data }));
};

// For Adding New Slide to Training Module
const addSlidePromise = async (library_id, module_id, slide, setSubmitting) => {
const response = await request(`/training/${library_id}/${module_id}/add_slide`, {
method: 'POST',
body: JSON.stringify({ slide }),
});
setSubmitting(false);
if (!response.ok) {
logErrorMessage(response);
const data = await response.json();
response.responseText = data;
throw response;
}
return response.json();
};

export const addSlide = (library_id, module_id, slide, setSubmitting) => (dispatch) => {
return addSlidePromise(library_id, module_id, slide, setSubmitting)
.then(() => window.location.reload())
.catch((error) => {
performValidation(error, dispatch, slideValidationRules);
});
};

// For Removing Slide from Training Module
const removeSlidesPromise = async (module_id, slideSlugList) => {
const response = await request(`/training/${module_id}/remove_slide`, {
method: 'DELETE',
body: JSON.stringify({ slideSlugList }),
});
if (!response.ok) {
logErrorMessage(response);
const data = await response.json();
response.responseText = data;
throw response;
}
return response.json();
};

export const removeSlides = (module_id, slideSlugList) => (dispatch) => {
return removeSlidesPromise(module_id, slideSlugList)
.then(() => window.location.reload())
.catch(data => dispatch({ type: API_FAIL, data }));
};
122 changes: 122 additions & 0 deletions app/assets/javascripts/training/components/modals/add_slide.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import Modal from '../../../components/common/modal.jsx';
import TextInput from '../../../components/common/text_input.jsx';
import { addSlide } from '../../../actions/training_modification_actions.js';
import { setValid, setInvalid, activateValidations, resetValidations } from '../../../actions/validation_actions.js';
import { firstValidationErrorMessage } from '../../../selectors';

const AddSlide = (props) => {
const [submitting, setSubmitting] = useState(false);
const [slide, setSlide] = useState({
title: '',
slug: '',
wiki_page: '',
});
const firstErrorMessage = useSelector(state => firstValidationErrorMessage(state));
const { library_id, module_id } = useParams();
const dispatch = useDispatch();

const handleInputChange = (key, value) => {
setSlide(prevSlide => ({
...prevSlide,
[key]: value
}));
};

useEffect(() => {
dispatch(resetValidations());
}, []);

const validateFields = () => {
let valid = true;
const message = I18n.t('training.validation_message');
if (!slide.title.trim()) {
dispatch(setInvalid('title', message));
valid = false;
} else {
dispatch(setValid('title'));
}

if (!slide.slug.trim()) {
dispatch(setInvalid('slug', message));
valid = false;
} else {
dispatch(setValid('slug'));
}

if (!slide.wiki_page.trim()) {
dispatch(setInvalid('wiki_page', message));
valid = false;
} else {
dispatch(setValid('wiki_page'));
}

return valid;
};

const submitHandler = () => {
dispatch(activateValidations());

if (validateFields()) {
setSubmitting(true);
dispatch(addSlide(library_id, module_id, slide, setSubmitting));
}
};

let formStyle;
if (submitting) {
formStyle = { pointerEvents: 'none', opacity: '0.5' };
}

return (
<>
<Modal >
<div className="container training-modification">
<div className="wizard__panel active training_modal single_column" style={formStyle}>
<h3>{I18n.t('training.add_slide')}</h3>
<p>{I18n.t('training.add_slide_msg')}</p>
<div>
<TextInput
id="title"
onChange={handleInputChange}
value={slide.title}
value_key="title"
required
editable
label={I18n.t('training.slide_title')}
placeholder={`${I18n.t('training.enter')} ${I18n.t('training.slide_title')}`}
/>
<TextInput
id="slug"
onChange={handleInputChange}
value={slide.slug}
value_key="slug"
required
editable
label={I18n.t('training.slide_slug')}
placeholder={`${I18n.t('training.enter')} ${I18n.t('training.slide_slug')}`}
/>
<TextInput
id="wiki_page"
onChange={handleInputChange}
value={slide.wiki_page}
value_key="wiki_page"
required
editable
label={I18n.t('training.slide_wiki_page')}
placeholder={`${I18n.t('training.enter')} ${I18n.t('training.slide_wiki_page')}`}
/>
<button className="button light" onClick={props.toggleModal}>{I18n.t('training.cancel')}</button>
<span className="validation-error"> &nbsp; {firstErrorMessage || '\xa0'}</span>
<button className="button dark right" onClick={submitHandler}>{I18n.t('training.add')}</button>
</div>
</div>
</div>
</Modal>
</>
);
};

export default AddSlide;
59 changes: 59 additions & 0 deletions app/assets/javascripts/training/components/modals/remove_slide.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import SelectableBox from '../../../components/common/selectable_box.jsx';
import { removeSlides } from '../../../actions/training_modification_actions.js';
import Modal from '../../../components/common/modal.jsx';

// Choose slides to remove from training module
const RemoveSlides = (props) => {
const [submitting, setSubmitting] = useState(false);
const { module_id } = useParams();
const [slideSlugList, setSlideSlugList] = useState([]);
const dispatch = useDispatch();

const handleSlideSelection = (selectedSlide) => {
setSlideSlugList((prev) => {
if (prev.includes(selectedSlide)) {
return prev.filter(slide => slide !== selectedSlide);
}
return [...prev, selectedSlide];
}
);
};

const submitHandler = () => {
setSubmitting(true);
dispatch(removeSlides(module_id, slideSlugList, setSubmitting));
};

const formClassName = submitting ? 'form-submitting' : '';

return (
<Modal>
<div className="container training-modification">
<div className={`wizard__panel active training_modal single_column remove-slide ${formClassName}`}>
<h3>{I18n.t('training.remove_slide')}</h3>
<p>{I18n.t('training.remove_slide_msg')}</p>
<div className="remove-slide-container" style={{ paddingBottom: '20px' }}>
{props.slidesAry.map(slide => (
<SelectableBox
key={slide.slug}
onClick={() => handleSlideSelection(slide.slug)}
heading={slide.title}
description={slide.wiki_page}
selected={slideSlugList.includes(slide.slug)}
/>
))}
</div>
<button className="button light" onClick={props.toggleModal}>{I18n.t('training.back')}</button>
<button className="button dark right" onClick={submitHandler} disabled={!slideSlugList.length}>
{I18n.t('training.remove')}
</button>
</div>
</div>
</Modal>
);
};

export default RemoveSlides;
Loading
Loading