mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-03-15 12:29:16 +01:00
(quality) Refacto pack-form
This commit is contained in:
parent
a05ef1f0ba
commit
7208cd80b0
@ -0,0 +1,70 @@
|
||||
import { PencilSimple, Trash } from 'phosphor-react';
|
||||
import * as React from 'react';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from './fab-button';
|
||||
import { FabModal } from './fab-modal';
|
||||
|
||||
interface EditDestroyButtonsProps {
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
onEdit: () => void,
|
||||
itemId: number,
|
||||
itemType: string,
|
||||
apiDestroy: (itemId: number) => Promise<void>,
|
||||
confirmationMessage?: string|ReactNode,
|
||||
className?: string,
|
||||
iconSize?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a group of two buttons.
|
||||
* Destroy : shows a modal dialog to ask the user for confirmation about the deletion of the provided item.
|
||||
* Edit : triggers the provided function.
|
||||
*/
|
||||
export const EditDestroyButtons: React.FC<EditDestroyButtonsProps> = ({ onSuccess, onError, onEdit, itemId, itemType, apiDestroy, confirmationMessage, className, iconSize = 20 }) => {
|
||||
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.edit_destroy_buttons.deleted', { TYPE: itemType }));
|
||||
}).catch((error) => {
|
||||
onError(t('app.admin.edit_destroy_buttons.unable_to_delete', { TYPE: itemType }) + error);
|
||||
});
|
||||
toggleDeletionModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`edit-destroy-buttons ${className || ''}`}>
|
||||
<FabButton className='edit-btn' onClick={onEdit}>
|
||||
<PencilSimple size={iconSize} weight="fill" />
|
||||
</FabButton>
|
||||
<FabButton type='button' className='delete-btn' onClick={toggleDeletionModal}>
|
||||
<Trash size={iconSize} weight="fill" />
|
||||
</FabButton>
|
||||
</div>
|
||||
<FabModal title={t('app.admin.edit_destroy_buttons.delete_item', { TYPE: itemType })}
|
||||
isOpen={deletionModal}
|
||||
toggleModal={toggleDeletionModal}
|
||||
closeButton={true}
|
||||
confirmButton={t('app.admin.edit_destroy_buttons.confirm_delete')}
|
||||
onConfirm={onDeleteConfirmed}>
|
||||
<span>{confirmationMessage || t('app.admin.edit_destroy_buttons.delete_confirmation', { TYPE: itemType })}</span>
|
||||
</FabModal>
|
||||
</>
|
||||
);
|
||||
};
|
@ -5,9 +5,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FabPopover } from '../../base/fab-popover';
|
||||
import { CreatePack } from './create-pack';
|
||||
import PrepaidPackAPI from '../../../api/prepaid-pack';
|
||||
import { EditPack } from './edit-pack';
|
||||
import FormatLib from '../../../lib/format';
|
||||
import { DestroyButton } from '../../base/destroy-button';
|
||||
import { EditDestroyButtons } from '../../base/edit-destroy-buttons';
|
||||
import { FabModal } from '../../base/fab-modal';
|
||||
import { PackForm } from './pack-form';
|
||||
|
||||
interface ConfigurePacksButtonProps {
|
||||
packsData: Array<PrepaidPack>,
|
||||
@ -27,6 +28,8 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
|
||||
|
||||
const [packs, setPacks] = useState<Array<PrepaidPack>>(packsData);
|
||||
const [showList, setShowList] = useState<boolean>(false);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [packData, setPackData] = useState<PrepaidPack>(null);
|
||||
|
||||
/**
|
||||
* Return the number of hours, user-friendly formatted
|
||||
@ -58,10 +61,41 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
|
||||
*/
|
||||
const renderAddButton = (): ReactNode => {
|
||||
return <CreatePack onSuccess={handleSuccess}
|
||||
onError={onError}
|
||||
groupId={groupId}
|
||||
priceableId={priceableId}
|
||||
priceableType={priceableType} />;
|
||||
onError={onError}
|
||||
groupId={groupId}
|
||||
priceableId={priceableId}
|
||||
priceableType={priceableType} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open/closes the "edit pack" modal dialog
|
||||
*/
|
||||
const toggleModal = (): void => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
/**
|
||||
* When the user clicks on the edition button, query the full data of the current pack from the API, then open te edition modal
|
||||
*/
|
||||
const handleRequestEdit = (pack: PrepaidPack): void => {
|
||||
PrepaidPackAPI.get(pack.id)
|
||||
.then(data => {
|
||||
setPackData(data);
|
||||
toggleModal();
|
||||
})
|
||||
.catch(error => onError(error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user has validated the changes of the PrepaidPack
|
||||
*/
|
||||
const handleUpdate = (pack: PrepaidPack): void => {
|
||||
PrepaidPackAPI.update(pack)
|
||||
.then(() => {
|
||||
handleSuccess(t('app.admin.configure_packs_button.pack_successfully_updated'));
|
||||
toggleModal();
|
||||
})
|
||||
.catch(error => onError(error));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -74,16 +108,22 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
|
||||
{packs?.map(p =>
|
||||
<li key={p.id} className={p.disabled ? 'disabled' : ''}>
|
||||
{formatDuration(p.minutes)} - {FormatLib.price(p.amount)}
|
||||
<span className="pack-actions">
|
||||
<EditPack 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>
|
||||
<EditDestroyButtons className='pack-actions'
|
||||
onError={onError}
|
||||
onSuccess={handleSuccess}
|
||||
onEdit={() => handleRequestEdit(p)}
|
||||
itemId={p.id}
|
||||
itemType={t('app.admin.configure_packs_button.pack')}
|
||||
apiDestroy={PrepaidPackAPI.destroy}/>
|
||||
<FabModal isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
title={t('app.admin.configure_packs_button.edit_pack')}
|
||||
className="edit-pack-modal"
|
||||
closeButton
|
||||
confirmButton={t('app.admin.configure_packs_button.confirm_changes')}
|
||||
onConfirmSendFormId="edit-pack">
|
||||
{packData && <PackForm formId="edit-pack" onSubmit={handleUpdate} pack={packData} />}
|
||||
</FabModal>
|
||||
</li>)}
|
||||
</ul>
|
||||
{packs?.length === 0 && <span>{t('app.admin.configure_packs_button.no_packs')}</span>}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { BaseSyntheticEvent } from 'react';
|
||||
import * as React from 'react';
|
||||
import Select from 'react-select';
|
||||
import Switch from 'react-switch';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { PrepaidPack } from '../../../models/prepaid-pack';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useImmer } from 'use-immer';
|
||||
import { FabInput } from '../../base/fab-input';
|
||||
import { IFablab } from '../../../models/fablab';
|
||||
import { SelectOption } from '../../../models/select';
|
||||
import { FormInput } from '../../form/form-input';
|
||||
import { FormSelect } from '../../form/form-select';
|
||||
import { FormSwitch } from '../../form/form-switch';
|
||||
import { FabInput } from '../../base/fab-input';
|
||||
|
||||
declare let Fablab: IFablab;
|
||||
|
||||
@ -25,10 +25,18 @@ type interval = typeof ALL_INTERVALS[number];
|
||||
* The form validation must be created elsewhere, using the attribute form={formId}.
|
||||
*/
|
||||
export const PackForm: React.FC<PackFormProps> = ({ formId, onSubmit, pack }) => {
|
||||
const [packData, updatePackData] = useImmer<PrepaidPack>(pack || {} as PrepaidPack);
|
||||
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { handleSubmit, register, control, formState, setValue } = useForm<PrepaidPack>({ defaultValues: { ...pack } });
|
||||
|
||||
const [formattedDuration, setFormattedDuration] = useState<number>(pack?.minutes || 60);
|
||||
/**
|
||||
* Callback triggered when the user validates the form
|
||||
*/
|
||||
const submitForm: SubmitHandler<PrepaidPack> = (data:PrepaidPack) => {
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert all validity-intervals to the react-select format
|
||||
*/
|
||||
@ -41,101 +49,65 @@ export const PackForm: React.FC<PackFormProps> = ({ formId, onSubmit, pack }) =>
|
||||
*/
|
||||
const intervalToOption = (value: interval): SelectOption<interval> => {
|
||||
if (!value) return { value, label: '' };
|
||||
|
||||
return { value, label: t(`app.admin.pack_form.intervals.${value}`, { COUNT: packData.validity_count || 0 }) };
|
||||
return { value, label: t(`app.admin.pack_form.intervals.${value}`, { COUNT: pack?.validity_count || 0 }) };
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user sends the form.
|
||||
* Changes hours into minutes
|
||||
*/
|
||||
const handleSubmit = (event: BaseSyntheticEvent): void => {
|
||||
event.preventDefault();
|
||||
onSubmit(packData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user inputs an amount for the current pack.
|
||||
*/
|
||||
const handleUpdateAmount = (amount: string) => {
|
||||
updatePackData(draft => {
|
||||
draft.amount = parseFloat(amount);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user inputs a number of hours for the current pack.
|
||||
*/
|
||||
const handleUpdateHours = (hours: string) => {
|
||||
updatePackData(draft => {
|
||||
draft.minutes = parseInt(hours, 10) * 60;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user inputs a number of periods for the current pack.
|
||||
*/
|
||||
const handleUpdateValidityCount = (count: string) => {
|
||||
updatePackData(draft => {
|
||||
draft.validity_count = parseInt(count, 10);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user selects a type of interval for the current pack.
|
||||
*/
|
||||
const handleUpdateValidityInterval = (option: SelectOption<interval>) => {
|
||||
updatePackData(draft => {
|
||||
draft.validity_interval = option.value as interval;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user disables the pack.
|
||||
*/
|
||||
const handleUpdateDisabled = (checked: boolean) => {
|
||||
updatePackData(draft => {
|
||||
draft.disabled = checked;
|
||||
});
|
||||
const formatDuration = (value) => {
|
||||
setFormattedDuration(value * 60);
|
||||
};
|
||||
useEffect(() => {
|
||||
setValue('minutes', formattedDuration);
|
||||
}, [formattedDuration]);
|
||||
|
||||
return (
|
||||
<form id={formId} onSubmit={handleSubmit} className="pack-form">
|
||||
<label htmlFor="hours">{t('app.admin.pack_form.hours')} *</label>
|
||||
<FabInput id="hours"
|
||||
type="number"
|
||||
defaultValue={packData?.minutes / 60 || ''}
|
||||
onChange={handleUpdateHours}
|
||||
min={1}
|
||||
icon={<i className="fas fa-clock" />}
|
||||
required />
|
||||
<label htmlFor="amount">{t('app.admin.pack_form.amount')} *</label>
|
||||
<FabInput id="amount"
|
||||
<form id={formId} onSubmit={handleSubmit(submitForm)} className="pack-form">
|
||||
<div className="duration">
|
||||
<label htmlFor="minutes">{t('app.admin.pack_form.hours')}</label>
|
||||
<Controller control={control}
|
||||
name='minutes'
|
||||
render={() => (
|
||||
<FabInput id="minutes"
|
||||
type='number'
|
||||
min={1}
|
||||
required
|
||||
icon={<i className="fas fa-clock" />}
|
||||
onChange={formatDuration}
|
||||
defaultValue={formattedDuration / 60} />
|
||||
)} />
|
||||
</div>
|
||||
|
||||
<FormInput id="amount"
|
||||
register={register}
|
||||
formState={formState}
|
||||
type="number"
|
||||
step={0.01}
|
||||
min={0}
|
||||
defaultValue={packData?.amount || ''}
|
||||
onChange={handleUpdateAmount}
|
||||
icon={<i className="fas fa-money-bill" />}
|
||||
addOn={Fablab.intl_currency}
|
||||
required />
|
||||
<label htmlFor="validity_count">{t('app.admin.pack_form.validity_count')}</label>
|
||||
rules={{ required: true, min: 0 }}
|
||||
label={t('app.admin.pack_form.amount')} />
|
||||
|
||||
<label className='validity' htmlFor="validity_count">{t('app.admin.pack_form.validity_count')}</label>
|
||||
<div className="interval-inputs">
|
||||
<FabInput id="validity_count"
|
||||
<FormInput id="validity_count"
|
||||
register={register}
|
||||
formState={formState}
|
||||
type="number"
|
||||
min={0}
|
||||
defaultValue={packData?.validity_count || ''}
|
||||
onChange={handleUpdateValidityCount}
|
||||
icon={<i className="fas fa-calendar-week" />} />
|
||||
<Select placeholder={t('app.admin.pack_form.select_interval')}
|
||||
className="select-interval"
|
||||
defaultValue={intervalToOption(packData?.validity_interval)}
|
||||
onChange={handleUpdateValidityInterval}
|
||||
options={buildOptions()} />
|
||||
</div>
|
||||
<label htmlFor="disabled">{t('app.admin.pack_form.disabled')}</label>
|
||||
<div>
|
||||
<Switch checked={packData?.disabled || false} onChange={handleUpdateDisabled} id="disabled" />
|
||||
icon={<i className="fas fa-calendar-week" />}
|
||||
rules={{ min: 0 }}/>
|
||||
<FormSelect id="validity_interval"
|
||||
control={control}
|
||||
options={buildOptions()}
|
||||
className="select-interval"
|
||||
placeholder={t('app.admin.pack_form.select_interval')}/>
|
||||
</div>
|
||||
|
||||
<FormSwitch id="disabled"
|
||||
control={control}
|
||||
formState={formState}
|
||||
label={t('app.admin.pack_form.disabled')} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
@ -7,13 +7,13 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import Select from 'react-select';
|
||||
import { SelectOption } from '../../models/select';
|
||||
import { CalendarBlank, PencilSimple } from 'phosphor-react';
|
||||
import { CalendarBlank } 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';
|
||||
import { EditDestroyButtons } from '../base/edit-destroy-buttons';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -136,10 +136,17 @@ export const Trainings: React.FC<TrainingsProps> = ({ onError, onSuccess }) => {
|
||||
<p>{training.name}</p>
|
||||
</div>
|
||||
|
||||
{(hasMachines(training) && <div className='machines'>
|
||||
<div className="machines">
|
||||
<span>{t('app.admin.trainings.associated_machines')}</span>
|
||||
<p>{machinesNames(training.machine_ids)}</p>
|
||||
</div>) || <div/>}
|
||||
{(hasMachines(training) &&
|
||||
<p>{machinesNames(training.machine_ids)}</p>
|
||||
) || <p>---</p>}
|
||||
</div>
|
||||
|
||||
<div className='capacity'>
|
||||
<span>{t('app.admin.trainings.capacity')}</span>
|
||||
<p>{training.nb_total_places || '---'}</p>
|
||||
</div>
|
||||
|
||||
<div className='cancel'>
|
||||
<span>{t('app.admin.trainings.cancellation')}</span>
|
||||
@ -150,11 +157,6 @@ export const Trainings: React.FC<TrainingsProps> = ({ onError, onSuccess }) => {
|
||||
</p>) || <p>---</p>}
|
||||
</div>
|
||||
|
||||
<div className='capacity'>
|
||||
<span>{t('app.admin.trainings.capacity')}</span>
|
||||
<p>{training.nb_total_places}</p>
|
||||
</div>
|
||||
|
||||
<div className='authorisation'>
|
||||
<span>{t('app.admin.trainings.authorisation')}</span>
|
||||
<p>
|
||||
@ -171,17 +173,13 @@ export const Trainings: React.FC<TrainingsProps> = ({ onError, onSuccess }) => {
|
||||
</div>
|
||||
|
||||
<div className='actions'>
|
||||
<div className='grpBtn'>
|
||||
<FabButton className='edit-btn' onClick={() => toTrainingEdit(training)}>
|
||||
<PencilSimple size={20} weight="fill" />
|
||||
</FabButton>
|
||||
<DestroyButton onSuccess={onSuccess}
|
||||
className="delete-btn"
|
||||
onError={onError}
|
||||
itemId={training.id}
|
||||
itemType={t('app.admin.trainings.training')}
|
||||
apiDestroy={TrainingAPI.destroy} />
|
||||
</div>
|
||||
<EditDestroyButtons className='grpBtn'
|
||||
onError={onError}
|
||||
onSuccess={onSuccess}
|
||||
onEdit={() => toTrainingEdit(training)}
|
||||
itemId={training.id}
|
||||
itemType={t('app.admin.trainings.training')}
|
||||
apiDestroy={TrainingAPI.destroy}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -23,6 +23,7 @@
|
||||
@import "modules/authentication-provider/openid-connect-data-mapping-form";
|
||||
@import "modules/authentication-provider/provider-form";
|
||||
@import "modules/authentication-provider/type-mapping-modal";
|
||||
@import "modules/base/edit-destroy-buttons";
|
||||
@import "modules/base/editorial-block";
|
||||
@import "modules/base/fab-alert";
|
||||
@import "modules/base/fab-button";
|
||||
|
@ -0,0 +1,15 @@
|
||||
.edit-destroy-buttons {
|
||||
button {
|
||||
@include btn;
|
||||
border-radius: 0;
|
||||
color: var(--gray-soft-lightest);
|
||||
&.edit-btn {background: var(--gray-hard-darkest) }
|
||||
&.delete-btn {background: var(--main) }
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.75;
|
||||
color: var(--gray-soft-lightest);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -22,10 +22,14 @@
|
||||
.popover-content {
|
||||
ul {
|
||||
padding-left: 19px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
&::before {
|
||||
content: '\f466';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
@ -37,12 +41,10 @@
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.pack-actions button {
|
||||
min-height: unset;
|
||||
font-size: 10px;
|
||||
vertical-align: middle;
|
||||
line-height: 10px;
|
||||
height: auto;
|
||||
.pack-actions {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
@ -7,4 +7,22 @@
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.duration {
|
||||
margin-bottom: 1.6rem;
|
||||
label {
|
||||
@include text-sm;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: "*";
|
||||
margin-left: 0.5ch;
|
||||
color: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
.validity {
|
||||
@include text-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -82,14 +82,6 @@
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
border-radius: var(--border-radius-sm);
|
||||
button {
|
||||
@include btn;
|
||||
border-radius: 0;
|
||||
color: var(--gray-soft-lightest);
|
||||
&:hover { opacity: 0.75; }
|
||||
}
|
||||
.edit-btn {background: var(--gray-hard-darkest) }
|
||||
.delete-btn {background: var(--main) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
<trainings-settings on-error="onError" on-success="onSuccess"></trainings-settings>
|
||||
</uib-tab>
|
||||
<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="onSuccess"></trainings>
|
||||
|
||||
<div class="m-t m-b">
|
||||
<button type="button" class="btn btn-warning" ui-sref="app.admin.trainings_new" ng-show="isAuthorized('admin')">
|
||||
|
@ -1,7 +1,7 @@
|
||||
en:
|
||||
app:
|
||||
admin:
|
||||
destroy_button:
|
||||
edit_destroy_buttons:
|
||||
deleted: "The {TYPE} was successfully deleted."
|
||||
unable_to_delete: "Unable to delete the {TYPE}: "
|
||||
delete_item: "Delete the {TYPE}"
|
||||
@ -681,6 +681,9 @@ en:
|
||||
no_packs: "No packs for now"
|
||||
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."
|
||||
edit_pack: "Edit the pack"
|
||||
confirm_changes: "Confirm changes"
|
||||
pack_successfully_updated: "The prepaid pack was successfully updated."
|
||||
configure_extended_prices_button:
|
||||
extended_prices: "Extended prices"
|
||||
no_extended_prices: "No extended price for now"
|
||||
@ -704,10 +707,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."
|
||||
create_pack: "Create this pack"
|
||||
pack_successfully_created: "The new prepaid pack was successfully created."
|
||||
edit_pack:
|
||||
edit_pack: "Edit the pack"
|
||||
confirm_changes: "Confirm changes"
|
||||
pack_successfully_updated: "The prepaid pack was successfully updated."
|
||||
create_extended_price:
|
||||
new_extended_price: "New extended price"
|
||||
new_extended_price_info: "Extended prices allows you to define prices based on custom durations, instead of the default hourly rates."
|
||||
|
Loading…
x
Reference in New Issue
Block a user