mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-04-10 00:53:51 +02:00
(ui) display trainings in list
This commit is contained in:
parent
117bd36caa
commit
305b5425bc
@ -28,4 +28,9 @@ export default class TrainingAPI {
|
|||||||
});
|
});
|
||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async destroy (trainingId: number): Promise<void> {
|
||||||
|
const res: AxiosResponse<void> = await apiClient.delete(`/api/trainings/${trainingId}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import { ReactNode, useState } from 'react';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FabButton } from './fab-button';
|
||||||
|
import { FabModal } from './fab-modal';
|
||||||
|
import { Trash } from 'phosphor-react';
|
||||||
|
|
||||||
|
interface DestroyButtonProps {
|
||||||
|
onSuccess: (message: string) => void,
|
||||||
|
onError: (message: string) => void,
|
||||||
|
itemId: number,
|
||||||
|
itemType: string,
|
||||||
|
apiDestroy: (itemId: number) => Promise<void>,
|
||||||
|
confirmationMessage?: string|ReactNode,
|
||||||
|
className?: string,
|
||||||
|
iconSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component shows a button.
|
||||||
|
* When clicked, we show a modal dialog to ask the user for confirmation about the deletion of the provided item.
|
||||||
|
*/
|
||||||
|
export const DestroyButton: React.FC<DestroyButtonProps> = ({ onSuccess, onError, itemId, itemType, apiDestroy, confirmationMessage, className, iconSize = 24 }) => {
|
||||||
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
|
const [deletionModal, setDeletionModal] = useState<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens/closes the deletion modal
|
||||||
|
*/
|
||||||
|
const toggleDeletionModal = (): void => {
|
||||||
|
setDeletionModal(!deletionModal);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deletion has been confirmed by the user.
|
||||||
|
* Call the API to trigger the deletion of the given item
|
||||||
|
*/
|
||||||
|
const onDeleteConfirmed = (): void => {
|
||||||
|
apiDestroy(itemId).then(() => {
|
||||||
|
onSuccess(t('app.admin.destroy_button.deleted', { TYPE: itemType }));
|
||||||
|
}).catch((error) => {
|
||||||
|
onError(t('app.admin.destroy_button.unable_to_delete', { TYPE: itemType }) + error);
|
||||||
|
});
|
||||||
|
toggleDeletionModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`destroy-button ${className || ''}`}>
|
||||||
|
<FabButton type='button' className="destroy-button-cta" icon={<Trash size={iconSize} weight="fill" />} onClick={toggleDeletionModal} />
|
||||||
|
<FabModal title={t('app.admin.destroy_button.delete_item', { TYPE: itemType })}
|
||||||
|
isOpen={deletionModal}
|
||||||
|
toggleModal={toggleDeletionModal}
|
||||||
|
closeButton={true}
|
||||||
|
confirmButton={t('app.admin.destroy_button.confirm_delete')}
|
||||||
|
onConfirm={onDeleteConfirmed}>
|
||||||
|
<span>{confirmationMessage || t('app.admin.destroy_button.delete_confirmation', { TYPE: itemType })}</span>
|
||||||
|
</FabModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FabPopover } from '../../base/fab-popover';
|
import { FabPopover } from '../../base/fab-popover';
|
||||||
import { CreatePack } from './create-pack';
|
import { CreatePack } from './create-pack';
|
||||||
import PrepaidPackAPI from '../../../api/prepaid-pack';
|
import PrepaidPackAPI from '../../../api/prepaid-pack';
|
||||||
import { DeletePack } from './delete-pack';
|
|
||||||
import { EditPack } from './edit-pack';
|
import { EditPack } from './edit-pack';
|
||||||
import FormatLib from '../../../lib/format';
|
import FormatLib from '../../../lib/format';
|
||||||
|
import { DestroyButton } from '../../base/destroy-button';
|
||||||
|
|
||||||
interface ConfigurePacksButtonProps {
|
interface ConfigurePacksButtonProps {
|
||||||
packsData: Array<PrepaidPack>,
|
packsData: Array<PrepaidPack>,
|
||||||
@ -76,7 +76,13 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
|
|||||||
{formatDuration(p.minutes)} - {FormatLib.price(p.amount)}
|
{formatDuration(p.minutes)} - {FormatLib.price(p.amount)}
|
||||||
<span className="pack-actions">
|
<span className="pack-actions">
|
||||||
<EditPack onSuccess={handleSuccess} onError={onError} pack={p} />
|
<EditPack onSuccess={handleSuccess} onError={onError} pack={p} />
|
||||||
<DeletePack onSuccess={handleSuccess} onError={onError} pack={p} />
|
<DestroyButton onSuccess={handleSuccess}
|
||||||
|
onError={onError}
|
||||||
|
itemId={p.id}
|
||||||
|
itemType={t('app.admin.configure_packs_button.pack')}
|
||||||
|
apiDestroy={PrepaidPackAPI.destroy}
|
||||||
|
iconSize={12}
|
||||||
|
confirmationMessage={t('app.admin.configure_packs_button.delete_confirmation')} />
|
||||||
</span>
|
</span>
|
||||||
</li>)}
|
</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FabButton } from '../../base/fab-button';
|
|
||||||
import { FabModal } from '../../base/fab-modal';
|
|
||||||
import { Loader } from '../../base/loader';
|
|
||||||
import { PrepaidPack } from '../../../models/prepaid-pack';
|
|
||||||
import PrepaidPackAPI from '../../../api/prepaid-pack';
|
|
||||||
|
|
||||||
interface DeletePackProps {
|
|
||||||
onSuccess: (message: string) => void,
|
|
||||||
onError: (message: string) => void,
|
|
||||||
pack: PrepaidPack,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component shows a button.
|
|
||||||
* When clicked, we show a modal dialog to ask the user for confirmation about the deletion of the provided pack.
|
|
||||||
*/
|
|
||||||
const DeletePack: React.FC<DeletePackProps> = ({ onSuccess, onError, pack }) => {
|
|
||||||
const { t } = useTranslation('admin');
|
|
||||||
|
|
||||||
const [deletionModal, setDeletionModal] = useState<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens/closes the deletion modal
|
|
||||||
*/
|
|
||||||
const toggleDeletionModal = (): void => {
|
|
||||||
setDeletionModal(!deletionModal);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The deletion has been confirmed by the user.
|
|
||||||
* Call the API to trigger the deletion of the temporary set plan-category
|
|
||||||
*/
|
|
||||||
const onDeleteConfirmed = (): void => {
|
|
||||||
PrepaidPackAPI.destroy(pack.id).then(() => {
|
|
||||||
onSuccess(t('app.admin.delete_pack.pack_deleted'));
|
|
||||||
}).catch((error) => {
|
|
||||||
onError(t('app.admin.delete_pack.unable_to_delete') + error);
|
|
||||||
});
|
|
||||||
toggleDeletionModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="delete-pack">
|
|
||||||
<FabButton type='button' className="remove-pack-button" icon={<i className="fa fa-trash" />} onClick={toggleDeletionModal} />
|
|
||||||
<FabModal title={t('app.admin.delete_pack.delete_pack')}
|
|
||||||
isOpen={deletionModal}
|
|
||||||
toggleModal={toggleDeletionModal}
|
|
||||||
closeButton={true}
|
|
||||||
confirmButton={t('app.admin.delete_pack.confirm_delete')}
|
|
||||||
onConfirm={onDeleteConfirmed}>
|
|
||||||
<span>{t('app.admin.delete_pack.delete_confirmation')}</span>
|
|
||||||
</FabModal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DeletePackWrapper: React.FC<DeletePackProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<Loader>
|
|
||||||
<DeletePack {...props} />
|
|
||||||
</Loader>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { DeletePackWrapper as DeletePack };
|
|
@ -1,15 +1,18 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { ErrorBoundary } from '../base/error-boundary';
|
import { ErrorBoundary } from '../base/error-boundary';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler, useWatch } from 'react-hook-form';
|
||||||
import { FormSwitch } from '../form/form-switch';
|
import { FormSwitch } from '../form/form-switch';
|
||||||
import { FormInput } from '../form/form-input';
|
import { FormInput } from '../form/form-input';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import { EditorialBlockForm } from '../editorial-block/editorial-block-form';
|
import { EditorialBlockForm } from '../editorial-block/editorial-block-form';
|
||||||
|
import { SettingName, SettingValue, trainingSettings } from '../../models/setting';
|
||||||
|
import SettingAPI from '../../api/setting';
|
||||||
|
import SettingLib from '../../lib/setting';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -21,20 +24,22 @@ interface TrainingsSettingsProps {
|
|||||||
/**
|
/**
|
||||||
* Trainings settings
|
* Trainings settings
|
||||||
*/
|
*/
|
||||||
export const TrainingsSettings: React.FC<TrainingsSettingsProps> = () => {
|
export const TrainingsSettings: React.FC<TrainingsSettingsProps> = ({ onError, onSuccess }) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
const { register, control, formState, handleSubmit } = useForm();
|
const { register, control, formState, handleSubmit, reset } = useForm<Record<SettingName, SettingValue>>();
|
||||||
|
|
||||||
const [isActiveAutoCancellation, setIsActiveAutoCancellation] = useState<boolean>(false);
|
const isActiveAutoCancellation = useWatch({ control, name: 'trainings_auto_cancel' }) as boolean;
|
||||||
const [isActiveAuthorizationValidity, setIsActiveAuthorizationValidity] = useState<boolean>(false);
|
const [isActiveAuthorizationValidity, setIsActiveAuthorizationValidity] = useState<boolean>(false);
|
||||||
const [isActiveValidationRule, setIsActiveValidationRule] = useState<boolean>(false);
|
const [isActiveValidationRule, setIsActiveValidationRule] = useState<boolean>(false);
|
||||||
|
|
||||||
/**
|
useEffect(() => {
|
||||||
* Callback triggered when the auto cancellation switch has changed.
|
SettingAPI.query(trainingSettings)
|
||||||
*/
|
.then(settings => {
|
||||||
const toggleAutoCancellation = (value: boolean) => {
|
const data = SettingLib.bulkMapToObject(settings);
|
||||||
setIsActiveAutoCancellation(value);
|
reset(data);
|
||||||
};
|
})
|
||||||
|
.catch(onError);
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the authorisation validity switch has changed.
|
* Callback triggered when the authorisation validity switch has changed.
|
||||||
@ -53,8 +58,12 @@ export const TrainingsSettings: React.FC<TrainingsSettingsProps> = () => {
|
|||||||
/**
|
/**
|
||||||
* Callback triggered when the form is submitted: save the settings
|
* Callback triggered when the form is submitted: save the settings
|
||||||
*/
|
*/
|
||||||
const onSubmit: SubmitHandler<any> = (data) => {
|
const onSubmit: SubmitHandler<Record<SettingName, SettingValue>> = (data) => {
|
||||||
console.log(data);
|
SettingAPI.bulkUpdate(SettingLib.objectToBulkMap(data)).then(() => {
|
||||||
|
onSuccess(t('app.admin.trainings_settings.update_success'));
|
||||||
|
}, reason => {
|
||||||
|
onError(reason);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -78,20 +87,20 @@ export const TrainingsSettings: React.FC<TrainingsSettingsProps> = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<FormSwitch id="active_auto_cancellation" control={control}
|
<FormSwitch id="trainings_auto_cancel" control={control}
|
||||||
onChange={toggleAutoCancellation} formState={formState}
|
formState={formState}
|
||||||
defaultValue={isActiveAutoCancellation}
|
defaultValue={isActiveAutoCancellation}
|
||||||
label={t('app.admin.trainings_settings.automatic_cancellation_switch')} />
|
label={t('app.admin.trainings_settings.automatic_cancellation_switch')} />
|
||||||
|
|
||||||
{isActiveAutoCancellation && <>
|
{isActiveAutoCancellation && <>
|
||||||
<FormInput id="auto_cancellation_threshold"
|
<FormInput id="trainings_auto_cancel_threshold"
|
||||||
type="number"
|
type="number"
|
||||||
register={register}
|
register={register}
|
||||||
rules={{ required: isActiveAutoCancellation, min: 0 }}
|
rules={{ required: isActiveAutoCancellation, min: 0 }}
|
||||||
step={1}
|
step={1}
|
||||||
formState={formState}
|
formState={formState}
|
||||||
label={t('app.admin.trainings_settings.automatic_cancellation_threshold')} />
|
label={t('app.admin.trainings_settings.automatic_cancellation_threshold')} />
|
||||||
<FormInput id="auto_cancellation_deadline"
|
<FormInput id="trainings_auto_cancel_deadline"
|
||||||
type="number"
|
type="number"
|
||||||
register={register}
|
register={register}
|
||||||
rules={{ required: isActiveAutoCancellation, min: 1 }}
|
rules={{ required: isActiveAutoCancellation, min: 1 }}
|
||||||
|
@ -7,7 +7,13 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { SelectOption } from '../../models/select';
|
import { SelectOption } from '../../models/select';
|
||||||
import { CalendarBlank, PencilSimple, Trash } from 'phosphor-react';
|
import { CalendarBlank, PencilSimple } from 'phosphor-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import type { Training } from '../../models/training';
|
||||||
|
import type { Machine } from '../../models/machine';
|
||||||
|
import TrainingAPI from '../../api/training';
|
||||||
|
import MachineAPI from '../../api/machine';
|
||||||
|
import { DestroyButton } from '../base/destroy-button';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -19,9 +25,13 @@ interface TrainingsProps {
|
|||||||
/**
|
/**
|
||||||
* Admin list of trainings
|
* Admin list of trainings
|
||||||
*/
|
*/
|
||||||
export const Trainings: React.FC<TrainingsProps> = () => {
|
export const Trainings: React.FC<TrainingsProps> = ({ onError, onSuccess }) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
|
const [trainings, setTrainings] = useState<Array<Training>>([]);
|
||||||
|
const [machines, setMachines] = useState<Array<Machine>>([]);
|
||||||
|
const [filter, setFilter] = useState<boolean>(null);
|
||||||
|
|
||||||
// Styles the React-select component
|
// Styles the React-select component
|
||||||
const customStyles = {
|
const customStyles = {
|
||||||
control: base => ({
|
control: base => ({
|
||||||
@ -35,18 +45,53 @@ export const Trainings: React.FC<TrainingsProps> = () => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MachineAPI.index({ disabled: false })
|
||||||
|
.then(setMachines)
|
||||||
|
.catch(onError);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
TrainingAPI.index({ disabled: filter })
|
||||||
|
.then(setTrainings)
|
||||||
|
.catch(onError);
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
/** Creates filtering options to the react-select format */
|
/** Creates filtering options to the react-select format */
|
||||||
const buildFilterOptions = (): Array<SelectOption<any>> => {
|
const buildFilterOptions = (): Array<SelectOption<boolean>> => {
|
||||||
return [
|
return [
|
||||||
{ value: 'all', label: t('app.admin.trainings.status_all') },
|
{ value: null, label: t('app.admin.trainings.status_all') },
|
||||||
{ value: 'enabled', label: t('app.admin.trainings.status_enabled') },
|
{ value: false, label: t('app.admin.trainings.status_enabled') },
|
||||||
{ value: 'disabled', label: t('app.admin.trainings.status_disabled') }
|
{ value: true, label: t('app.admin.trainings.status_disabled') }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Handel filter change */
|
/** Handel filter change */
|
||||||
const onFilterChange = (option: SelectOption<any>) => {
|
const onFilterChange = (option: SelectOption<boolean>) => {
|
||||||
console.log(option);
|
setFilter(option.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of machines names for teh given ids
|
||||||
|
*/
|
||||||
|
const machinesNames = (ids: Array<number>): string => {
|
||||||
|
return machines.filter(m => ids.includes(m.id)).map(m => m.name).join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Check if the given training has associated non-disabled machines
|
||||||
|
*/
|
||||||
|
const hasMachines = (training: Training): boolean => {
|
||||||
|
const activesMachines = machines.map(m => m.id);
|
||||||
|
return training.machine_ids.filter(id => activesMachines.includes(id)).length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the user to the given training edition page
|
||||||
|
*/
|
||||||
|
const toTrainingEdit = (training: Training): void => {
|
||||||
|
window.location.href = `/#!/admin/trainings/${training.id}/edit`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Link to calendar page */
|
/** Link to calendar page */
|
||||||
@ -84,61 +129,66 @@ export const Trainings: React.FC<TrainingsProps> = () => {
|
|||||||
{/* map
|
{/* map
|
||||||
ajouter la classe .is-override si l'item a au moins un réglage spécifique (différent des paramètres généraux)
|
ajouter la classe .is-override si l'item a au moins un réglage spécifique (différent des paramètres généraux)
|
||||||
*/}
|
*/}
|
||||||
<div className='trainings-list-item'>
|
{trainings.map(training => (
|
||||||
<div className='name'>
|
<div className='trainings-list-item' key={training.id}>
|
||||||
<span>{t('app.admin.trainings.name')}</span>
|
<div className='name'>
|
||||||
<p>All you can learn : super training</p>
|
<span>{t('app.admin.trainings.name')}</span>
|
||||||
</div>
|
<p>{training.name}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='machines'>
|
{(hasMachines(training) && <div className='machines'>
|
||||||
<span>{t('app.admin.trainings.associated_machines')}</span>
|
<span>{t('app.admin.trainings.associated_machines')}</span>
|
||||||
<p>Découpeuse laser, Découpeuse vinyle, Shopbot / Grande fraiseuse, Petite Fraiseuse, Imprimante 3D</p>
|
<p>{machinesNames(training.machine_ids)}</p>
|
||||||
</div>
|
</div>) || <div/>}
|
||||||
|
|
||||||
<div className='cancel'>
|
<div className='cancel'>
|
||||||
<span>{t('app.admin.trainings.cancellation')}</span>
|
<span>{t('app.admin.trainings.cancellation')}</span>
|
||||||
<p>5 {t('app.admin.trainings.cancellation_minimum')}<span>|</span>48 {t('app.admin.trainings.cancellation_deadline')}
|
<p>5 {t('app.admin.trainings.cancellation_minimum')}<span>|</span>48 {t('app.admin.trainings.cancellation_deadline')}
|
||||||
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
||||||
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='capacity'>
|
<div className='capacity'>
|
||||||
<span>{t('app.admin.trainings.capacity')}</span>
|
<span>{t('app.admin.trainings.capacity')}</span>
|
||||||
<p>10</p>
|
<p>{training.nb_total_places}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='authorisation'>
|
<div className='authorisation'>
|
||||||
<span>{t('app.admin.trainings.authorisation')}</span>
|
<span>{t('app.admin.trainings.authorisation')}</span>
|
||||||
<p>
|
<p>
|
||||||
{t('app.admin.trainings.active_true')}
|
{t('app.admin.trainings.active_true')}
|
||||||
<span>|</span>{t('app.admin.trainings.period_MONTH', { MONTH: 48 })}
|
<span>|</span>{t('app.admin.trainings.period_MONTH', { MONTH: 48 })}
|
||||||
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
||||||
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='rule'>
|
<div className='rule'>
|
||||||
<span>{t('app.admin.trainings.validation_rule')}</span>
|
<span>{t('app.admin.trainings.validation_rule')}</span>
|
||||||
<p>
|
<p>
|
||||||
{t('app.admin.trainings.active_false')}
|
{t('app.admin.trainings.active_false')}
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
{/* si l'item a un réglage spécifique (différent des paramètres généraux) */}
|
||||||
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
{true && <span className='override'>{t('app.admin.trainings.override')}</span> }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='actions'>
|
<div className='actions'>
|
||||||
<div className='grpBtn'>
|
<div className='grpBtn'>
|
||||||
<FabButton className='edit-btn'>
|
<FabButton className='edit-btn' onClick={() => toTrainingEdit(training)}>
|
||||||
<PencilSimple size={20} weight="fill" />
|
<PencilSimple size={20} weight="fill" />
|
||||||
</FabButton>
|
</FabButton>
|
||||||
<FabButton className='delete-btn'>
|
<DestroyButton onSuccess={onSuccess}
|
||||||
<Trash size={20} weight="fill" />
|
className="delete-btn"
|
||||||
</FabButton>
|
onError={onError}
|
||||||
|
itemId={training.id}
|
||||||
|
itemType={t('app.admin.trainings.training')}
|
||||||
|
apiDestroy={TrainingAPI.destroy} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -354,6 +354,20 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a success message forwarded from a child react component
|
||||||
|
*/
|
||||||
|
$scope.onSuccess = function (message) {
|
||||||
|
growl.success(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback triggered by react components
|
||||||
|
*/
|
||||||
|
$scope.onError = function (message) {
|
||||||
|
growl.error(message);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the feature-tour for the admin/trainings page.
|
* Setup the feature-tour for the admin/trainings page.
|
||||||
* This is intended as a contextual help (when pressing F1)
|
* This is intended as a contextual help (when pressing F1)
|
||||||
|
@ -233,6 +233,12 @@ export const storeSettings = [
|
|||||||
'store_hidden'
|
'store_hidden'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const trainingSettings = [
|
||||||
|
'trainings_auto_cancel',
|
||||||
|
'trainings_auto_cancel_threshold',
|
||||||
|
'trainings_auto_cancel_deadline'
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const allSettings = [
|
export const allSettings = [
|
||||||
...homePageSettings,
|
...homePageSettings,
|
||||||
...privacyPolicySettings,
|
...privacyPolicySettings,
|
||||||
@ -258,7 +264,8 @@ export const allSettings = [
|
|||||||
...pricingSettings,
|
...pricingSettings,
|
||||||
...poymentSettings,
|
...poymentSettings,
|
||||||
...displaySettings,
|
...displaySettings,
|
||||||
...storeSettings
|
...storeSettings,
|
||||||
|
...trainingSettings
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type SettingName = typeof allSettings[number];
|
export type SettingName = typeof allSettings[number];
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
@import "modules/authentication-provider/openid-connect-data-mapping-form";
|
@import "modules/authentication-provider/openid-connect-data-mapping-form";
|
||||||
@import "modules/authentication-provider/provider-form";
|
@import "modules/authentication-provider/provider-form";
|
||||||
@import "modules/authentication-provider/type-mapping-modal";
|
@import "modules/authentication-provider/type-mapping-modal";
|
||||||
|
@import "modules/base/destroy-button";
|
||||||
@import "modules/base/editorial-block";
|
@import "modules/base/editorial-block";
|
||||||
@import "modules/base/fab-alert";
|
@import "modules/base/fab-alert";
|
||||||
@import "modules/base/fab-button";
|
@import "modules/base/fab-button";
|
||||||
@ -94,7 +95,6 @@
|
|||||||
@import "modules/pricing/editable-price";
|
@import "modules/pricing/editable-price";
|
||||||
@import "modules/pricing/machines/configure-packs-button";
|
@import "modules/pricing/machines/configure-packs-button";
|
||||||
@import "modules/pricing/machines/create-pack";
|
@import "modules/pricing/machines/create-pack";
|
||||||
@import "modules/pricing/machines/delete-pack";
|
|
||||||
@import "modules/pricing/machines/edit-pack";
|
@import "modules/pricing/machines/edit-pack";
|
||||||
@import "modules/pricing/machines/machines-pricing";
|
@import "modules/pricing/machines/machines-pricing";
|
||||||
@import "modules/pricing/machines/pack-form";
|
@import "modules/pricing/machines/pack-form";
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
.destroy-button {
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
& > button.destroy-button-cta {
|
||||||
|
background-color: var(--alert);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pack-actions button {
|
.pack-actions button {
|
||||||
|
min-height: unset;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
.delete-pack {
|
|
||||||
display: inline;
|
|
||||||
|
|
||||||
.remove-pack-button {
|
|
||||||
background-color: #cb1117;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,7 +26,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<uib-tabset justified="true" active="tabs.active">
|
<uib-tabset justified="true" active="tabs.active">
|
||||||
<uib-tab heading="{{ 'app.admin.trainings.trainings_settings' | translate }}" index="1" class="manage-trainings">
|
<uib-tab heading="{{ 'app.admin.trainings.trainings_settings' | translate }}" index="1" class="manage-trainings">
|
||||||
<trainings-settings on-error="onError" on-success="on-success"></trainings-settings>
|
<trainings-settings on-error="onError" on-success="onSuccess"></trainings-settings>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
<uib-tab heading="{{ 'app.admin.trainings.all_trainings' | translate }}" index="0" class="manage-trainings">
|
<uib-tab heading="{{ 'app.admin.trainings.all_trainings' | translate }}" index="0" class="manage-trainings">
|
||||||
<trainings on-error="onError" on-success="on-success"></trainings>
|
<trainings on-error="onError" on-success="on-success"></trainings>
|
||||||
|
@ -165,7 +165,10 @@ class Setting < ApplicationRecord
|
|||||||
advanced_accounting
|
advanced_accounting
|
||||||
external_id
|
external_id
|
||||||
prevent_invoices_zero
|
prevent_invoices_zero
|
||||||
invoice_VAT-name] }
|
invoice_VAT-name
|
||||||
|
trainings_auto_cancel
|
||||||
|
trainings_auto_cancel_threshold
|
||||||
|
trainings_auto_cancel_deadline] }
|
||||||
# WARNING: when adding a new key, you may also want to add it in:
|
# WARNING: when adding a new key, you may also want to add it in:
|
||||||
# - config/locales/en.yml#settings
|
# - config/locales/en.yml#settings
|
||||||
# - app/frontend/src/javascript/models/setting.ts#SettingName
|
# - app/frontend/src/javascript/models/setting.ts#SettingName
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
en:
|
en:
|
||||||
app:
|
app:
|
||||||
admin:
|
admin:
|
||||||
|
destroy_button:
|
||||||
|
deleted: "The {TYPE} was successfully deleted."
|
||||||
|
unable_to_delete: "Unable to delete the {TYPE}: "
|
||||||
|
delete_item: "Delete the {TYPE}"
|
||||||
|
confirm_delete: "Delete"
|
||||||
|
delete_confirmation: "Are you sure you want to delete this {TYPE}?"
|
||||||
machines:
|
machines:
|
||||||
the_fablab_s_machines: "The FabLab's machines"
|
the_fablab_s_machines: "The FabLab's machines"
|
||||||
all_machines: "All machines"
|
all_machines: "All machines"
|
||||||
@ -491,6 +497,7 @@ en:
|
|||||||
cta_label: "Button label"
|
cta_label: "Button label"
|
||||||
cta_url: "url"
|
cta_url: "url"
|
||||||
save: "Save"
|
save: "Save"
|
||||||
|
update_success: "The trainings settings were successfully updated"
|
||||||
#events tracking and management
|
#events tracking and management
|
||||||
events:
|
events:
|
||||||
events_monitoring: "Events monitoring"
|
events_monitoring: "Events monitoring"
|
||||||
@ -669,9 +676,11 @@ en:
|
|||||||
machines: "Machines"
|
machines: "Machines"
|
||||||
price_updated: "Price successfully updated"
|
price_updated: "Price successfully updated"
|
||||||
configure_packs_button:
|
configure_packs_button:
|
||||||
|
pack: "prepaid pack"
|
||||||
packs: "Prepaid packs"
|
packs: "Prepaid packs"
|
||||||
no_packs: "No packs for now"
|
no_packs: "No packs for now"
|
||||||
pack_DURATION: "{DURATION} hours"
|
pack_DURATION: "{DURATION} hours"
|
||||||
|
delete_confirmation: "Are you sure you want to delete this prepaid pack? This won't be possible if the pack was already bought by users."
|
||||||
configure_extended_prices_button:
|
configure_extended_prices_button:
|
||||||
extended_prices: "Extended prices"
|
extended_prices: "Extended prices"
|
||||||
no_extended_prices: "No extended price for now"
|
no_extended_prices: "No extended price for now"
|
||||||
@ -695,12 +704,6 @@ en:
|
|||||||
new_pack_info: "A prepaid pack allows users to buy {TYPE, select, Machine{machine} Space{space} other{}} hours before booking any slots. These packs can provide discounts on volumes purchases."
|
new_pack_info: "A prepaid pack allows users to buy {TYPE, select, Machine{machine} Space{space} other{}} hours before booking any slots. These packs can provide discounts on volumes purchases."
|
||||||
create_pack: "Create this pack"
|
create_pack: "Create this pack"
|
||||||
pack_successfully_created: "The new prepaid pack was successfully created."
|
pack_successfully_created: "The new prepaid pack was successfully created."
|
||||||
delete_pack:
|
|
||||||
pack_deleted: "The prepaid pack was successfully deleted."
|
|
||||||
unable_to_delete: "Unable to delete the prepaid pack: "
|
|
||||||
delete_pack: "Delete the prepaid pack"
|
|
||||||
confirm_delete: "Delete"
|
|
||||||
delete_confirmation: "Are you sure you want to delete this prepaid pack? This won't be possible if the pack was already bought by users."
|
|
||||||
edit_pack:
|
edit_pack:
|
||||||
edit_pack: "Edit the pack"
|
edit_pack: "Edit the pack"
|
||||||
confirm_changes: "Confirm changes"
|
confirm_changes: "Confirm changes"
|
||||||
|
@ -664,6 +664,9 @@ en:
|
|||||||
external_id: "external identifier"
|
external_id: "external identifier"
|
||||||
prevent_invoices_zero: "prevent building invoices at 0"
|
prevent_invoices_zero: "prevent building invoices at 0"
|
||||||
invoice_VAT-name: "VAT name"
|
invoice_VAT-name: "VAT name"
|
||||||
|
trainings_auto_cancel: "Trainings automatic cancellation"
|
||||||
|
trainings_auto_cancel_threshold: "Minimum participants for automatic cancellation"
|
||||||
|
trainings_auto_cancel_deadline: "Automatic cancellation deadline"
|
||||||
#statuses of projects
|
#statuses of projects
|
||||||
statuses:
|
statuses:
|
||||||
new: "New"
|
new: "New"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user