mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(ui) Plan limit form and modal
This commit is contained in:
parent
9294bc4c88
commit
c964ec8a6d
@ -25,6 +25,7 @@ import { PartnerModal } from './partner-modal';
|
||||
import { PlanPricingForm } from './plan-pricing-form';
|
||||
import { AdvancedAccountingForm } from '../accounting/advanced-accounting-form';
|
||||
import { FabTabs } from '../base/fab-tabs';
|
||||
import { PlanLimitForm } from './plan-limit-form';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -202,6 +203,9 @@ export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuc
|
||||
formState={formState}
|
||||
rules={{ required: true }} />
|
||||
</div>
|
||||
{action === 'update' && <FabAlert level="info">
|
||||
{t('app.admin.plan_form.edit_amount_info')}
|
||||
</FabAlert>}
|
||||
<FormInput register={register}
|
||||
formState={formState}
|
||||
id="amount"
|
||||
@ -268,24 +272,30 @@ export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuc
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{categories?.length > 0 && <FormSelect options={categories}
|
||||
formState={formState}
|
||||
control={control}
|
||||
id="plan_category_id"
|
||||
tooltip={t('app.admin.plan_form.category_help')}
|
||||
label={t('app.admin.plan_form.category')} />}
|
||||
{action === 'update' && <FabAlert level="warning">
|
||||
{t('app.admin.plan_form.edit_amount_info')}
|
||||
</FabAlert>}
|
||||
<section>
|
||||
<header>
|
||||
<p className="title">{t('app.admin.plan_form.display')} </p>
|
||||
</header>
|
||||
<div className="content">
|
||||
{categories?.length > 0 && <FormSelect options={categories}
|
||||
formState={formState}
|
||||
control={control}
|
||||
id="plan_category_id"
|
||||
tooltip={t('app.admin.plan_form.category_help')}
|
||||
label={t('app.admin.plan_form.category')} />}
|
||||
<FormInput register={register}
|
||||
formState={formState}
|
||||
id="ui_weight"
|
||||
type="number"
|
||||
label={t('app.admin.plan_form.visual_prominence')}
|
||||
tooltip={t('app.admin.plan_form.visual_prominence_help')} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<FormInput register={register}
|
||||
formState={formState}
|
||||
id="ui_weight"
|
||||
type="number"
|
||||
label={t('app.admin.plan_form.visual_prominence')}
|
||||
tooltip={t('app.admin.plan_form.visual_prominence_help')} />
|
||||
<section>
|
||||
<AdvancedAccountingForm register={register} onError={onError} />
|
||||
</section>
|
||||
|
||||
<AdvancedAccountingForm register={register} onError={onError} />
|
||||
{action === 'update' && <PlanPricingForm formState={formState}
|
||||
control={control}
|
||||
onError={onError}
|
||||
@ -315,7 +325,8 @@ export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuc
|
||||
{
|
||||
id: 'usageLimits',
|
||||
title: t('app.admin.plan_form.tab_usage_limits'),
|
||||
content: <pre>plop</pre>
|
||||
content: <PlanLimitForm control={control}
|
||||
formState={formState} />
|
||||
}
|
||||
]} />
|
||||
</form>
|
||||
|
110
app/frontend/src/javascript/components/plans/plan-limit-form.tsx
Normal file
110
app/frontend/src/javascript/components/plans/plan-limit-form.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { useState } from 'react';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FormSwitch } from '../form/form-switch';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { PencilSimple, Trash } from 'phosphor-react';
|
||||
import { PlanLimitModal } from './plan-limit-modal';
|
||||
|
||||
interface PlanLimitFormProps<TContext extends object> {
|
||||
control: Control<any, TContext>,
|
||||
formState: FormState<any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Form tab to manage a subscription's usage limit
|
||||
*/
|
||||
export const PlanLimitForm = <TContext extends object> ({ control, formState }: PlanLimitFormProps<TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
/**
|
||||
* Opens/closes the product stock edition modal
|
||||
*/
|
||||
const toggleModal = (): void => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="plan-limit-form">
|
||||
<section>
|
||||
<header>
|
||||
<p className="title">{t('app.admin.plan_limit_form.usage_limitation')}</p>
|
||||
<p className="description">{t('app.admin.plan_limit_form.usage_limitation_info')}</p>
|
||||
</header>
|
||||
<div className="content">
|
||||
<FormSwitch control={control}
|
||||
formState={formState}
|
||||
defaultValue={false}
|
||||
label={t('app.admin.plan_limit_form.usage_limitation_switch')}
|
||||
id="active_limitation" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="plan-limit-grp">
|
||||
<header>
|
||||
<p>{t('app.admin.plan_limit_form.all_limitations')}</p>
|
||||
<div className="grpBtn">
|
||||
<FabButton onClick={toggleModal} className="is-main">
|
||||
{t('app.admin.plan_limit_form.new_usage_limitation')}
|
||||
</FabButton>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className='plan-limit-list'>
|
||||
<p className="title">{t('app.admin.plan_limit_form.by_categories')}</p>
|
||||
<div className="plan-limit-item">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.category')}</span>
|
||||
<p>Plop</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>5</p>
|
||||
</div>
|
||||
|
||||
<div className='actions'>
|
||||
<div className='grpBtn'>
|
||||
<FabButton className='edit-btn'>
|
||||
<PencilSimple size={20} weight="fill" />
|
||||
</FabButton>
|
||||
<FabButton className='delete-btn'>
|
||||
<Trash size={20} weight="fill" />
|
||||
</FabButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='plan-limit-list'>
|
||||
<p className="title">{t('app.admin.plan_limit_form.by_machine')}</p>
|
||||
<div className="plan-limit-item">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.machine')}</span>
|
||||
<p>Pouet</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>5</p>
|
||||
</div>
|
||||
|
||||
<div className='actions'>
|
||||
<div className='grpBtn'>
|
||||
<FabButton className='edit-btn'>
|
||||
<PencilSimple size={20} weight="fill" />
|
||||
</FabButton>
|
||||
<FabButton className='delete-btn'>
|
||||
<Trash size={20} weight="fill" />
|
||||
</FabButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PlanLimitModal isOpen={isOpen}
|
||||
toggleModal={toggleModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { FormInput } from '../form/form-input';
|
||||
|
||||
type typeSelectOption = { value: any, label: string };
|
||||
interface PlanLimitModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to manage subscriptions limitations of use
|
||||
*/
|
||||
export const PlanLimitModal: React.FC<PlanLimitModalProps> = ({ isOpen, toggleModal }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { register, control, formState } = useForm<any>();
|
||||
const [limitType, setLimitType] = React.useState<'categories' | 'machine'>('categories');
|
||||
|
||||
/**
|
||||
* Toggle the form between 'categories' and 'machine'
|
||||
*/
|
||||
const toggleLimitType = (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>, type: 'categories' | 'machine') => {
|
||||
evt.preventDefault();
|
||||
setLimitType(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates options to the react-select format
|
||||
*/
|
||||
const buildMachinesCategoriesOptions = (): Array<typeSelectOption> => {
|
||||
return [
|
||||
{ value: '0', label: 'yep' },
|
||||
{ value: '1', label: 'nope' }
|
||||
];
|
||||
};
|
||||
/**
|
||||
* Creates options to the react-select format
|
||||
*/
|
||||
const buildMachinesOptions = (): Array<typeSelectOption> => {
|
||||
return [
|
||||
{ value: '0', label: 'pif' },
|
||||
{ value: '1', label: 'paf' },
|
||||
{ value: '2', label: 'pouf' }
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<FabModal title={t('app.admin.plan_limit_modal.title')}
|
||||
width={ModalSize.large}
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton>
|
||||
<form className='plan-limit-modal'>
|
||||
<p className='subtitle'>{t('app.admin.plan_limit_modal.limit_reservations')}</p>
|
||||
<div className="grp">
|
||||
<button onClick={evt => toggleLimitType(evt, 'categories')}
|
||||
className={limitType === 'categories' ? 'is-active' : ''}>
|
||||
{t('app.admin.plan_limit_modal.by_categories')}
|
||||
</button>
|
||||
<button onClick={evt => toggleLimitType(evt, 'machine')}
|
||||
className={limitType === 'machine' ? 'is-active' : ''}>
|
||||
{t('app.admin.plan_limit_modal.by_machine')}
|
||||
</button>
|
||||
</div>
|
||||
{limitType === 'categories' && <>
|
||||
<FabAlert level='info'>{t('app.admin.plan_limit_modal.categories_info')}</FabAlert>
|
||||
<FormSelect options={buildMachinesCategoriesOptions()}
|
||||
control={control}
|
||||
id="machines_category"
|
||||
rules={{ required: limitType === 'categories' }}
|
||||
formState={formState}
|
||||
label={t('app.admin.plan_limit_modal.category')} />
|
||||
</>}
|
||||
{limitType === 'machine' && <>
|
||||
<FabAlert level='info'>{t('app.admin.plan_limit_modal.machine_info')}</FabAlert>
|
||||
<FormSelect options={buildMachinesOptions()}
|
||||
control={control}
|
||||
id="machine"
|
||||
rules={{ required: limitType === 'machine' }}
|
||||
formState={formState}
|
||||
label={t('app.admin.plan_limit_modal.machine')} />
|
||||
</>}
|
||||
<FormInput id="hours_limit"
|
||||
type="number"
|
||||
register={register}
|
||||
rules={{ required: true, min: 1 }}
|
||||
step={1}
|
||||
formState={formState}
|
||||
label={t('app.admin.plan_limit_modal.max_hours_per_day')} />
|
||||
</form>
|
||||
</FabModal>
|
||||
);
|
||||
};
|
@ -92,32 +92,36 @@ export const PlanPricingForm = <TContext extends object>({ register, control, fo
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid="plan-pricing-form">
|
||||
<h4>{t('app.admin.plan_pricing_form.prices')}</h4>
|
||||
{plans && <FormSelect options={plans}
|
||||
label={t('app.admin.plan_pricing_form.copy_prices_from')}
|
||||
tooltip={t('app.admin.plan_pricing_form.copy_prices_from_help')}
|
||||
control={control}
|
||||
onChange={handleCopyPrices}
|
||||
id="parent_plan_id" />}
|
||||
{<FabTabs tabs={[
|
||||
machines && {
|
||||
id: 'machines',
|
||||
title: t('app.admin.plan_pricing_form.machines'),
|
||||
content: fields.map((price, index) => {
|
||||
if (price.priceable_type !== 'Machine') return false;
|
||||
return renderPriceElement(price, index);
|
||||
}).filter(Boolean)
|
||||
},
|
||||
spaces && {
|
||||
id: 'spaces',
|
||||
title: t('app.admin.plan_pricing_form.spaces'),
|
||||
content: fields.map((price, index) => {
|
||||
if (price.priceable_type !== 'Space') return false;
|
||||
return renderPriceElement(price, index);
|
||||
}).filter(Boolean)
|
||||
}
|
||||
]} />}
|
||||
</div>
|
||||
<section className="plan-pricing-form" data-testid="plan-pricing-form">
|
||||
<header>
|
||||
<p className="title">{t('app.admin.plan_pricing_form.prices')}</p>
|
||||
<p className="description">{t('app.admin.plan_pricing_form.copy_prices_from_help')}</p>
|
||||
</header>
|
||||
<div className="content">
|
||||
{plans && <FormSelect options={plans}
|
||||
control={control}
|
||||
label={t('app.admin.plan_pricing_form.copy_prices_from')}
|
||||
onChange={handleCopyPrices}
|
||||
id="parent_plan_id" />}
|
||||
{<FabTabs tabs={[
|
||||
machines && {
|
||||
id: 'machines',
|
||||
title: t('app.admin.plan_pricing_form.machines'),
|
||||
content: fields.map((price, index) => {
|
||||
if (price.priceable_type !== 'Machine') return false;
|
||||
return renderPriceElement(price, index);
|
||||
}).filter(Boolean)
|
||||
},
|
||||
spaces && {
|
||||
id: 'spaces',
|
||||
title: t('app.admin.plan_pricing_form.spaces'),
|
||||
content: fields.map((price, index) => {
|
||||
if (price.priceable_type !== 'Space') return false;
|
||||
return renderPriceElement(price, index);
|
||||
}).filter(Boolean)
|
||||
}
|
||||
]} />}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -178,7 +178,7 @@ export const ProductStockForm = <TContext extends object> ({ currentFormValues,
|
||||
<span>{t('app.admin.store.product_stock_form.external')}</span>
|
||||
<p>{currentFormValues?.stock?.external}</p>
|
||||
</div>
|
||||
<FabButton onClick={toggleModal} icon={<PencilSimple size={20} weight="fill" />} className="is-black">Modifier</FabButton>
|
||||
<FabButton onClick={toggleModal} icon={<PencilSimple size={20} weight="fill" />} className="is-black">{t('app.admin.store.product_stock_form.edit')}</FabButton>
|
||||
</div>
|
||||
|
||||
{fields.length > 0 && <div className="ongoing-stocks">
|
||||
|
@ -97,6 +97,9 @@
|
||||
@import "modules/plan-categories/plan-categories-list";
|
||||
@import "modules/plans/plan-card";
|
||||
@import "modules/plans/plan-form";
|
||||
@import "modules/plans/plan-limit-form";
|
||||
@import "modules/plans/plan-limit-modal";
|
||||
@import "modules/plans/plan-pricing-form";
|
||||
@import "modules/plans/plans-filter";
|
||||
@import "modules/plans/plans-list";
|
||||
@import "modules/prepaid-packs/packs-summary";
|
||||
|
@ -26,6 +26,7 @@
|
||||
background-color: var(--gray-soft-lightest);
|
||||
cursor: default;
|
||||
}
|
||||
&:focus { outline: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
flex-direction: column;
|
||||
gap: 3.2rem;
|
||||
|
||||
.fab-alert { margin: 0; }
|
||||
|
||||
section { @include layout-settings; }
|
||||
.save-btn { align-self: flex-start; }
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
flex-direction: column;
|
||||
gap: 3.2rem;
|
||||
|
||||
.fab-alert { margin: 0; }
|
||||
|
||||
section { @include layout-settings; }
|
||||
.save-btn { align-self: flex-start; }
|
||||
}
|
||||
|
@ -37,28 +37,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//.plan-sheet {
|
||||
// margin-top: 4rem;
|
||||
//}
|
||||
//.duration {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
|
||||
// .form-item:first-child {
|
||||
// margin-right: 32px;
|
||||
// }
|
||||
//}
|
||||
//.partner {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: flex-end;
|
||||
|
||||
// .fab-alert {
|
||||
// width: 100%;
|
||||
// }
|
||||
//}
|
||||
//.submit-btn {
|
||||
// float: right;
|
||||
//}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
.plan-limit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3.2rem 0;
|
||||
|
||||
section { @include layout-settings; }
|
||||
|
||||
.plan-limit-grp {
|
||||
header {
|
||||
@include header();
|
||||
p {
|
||||
@include title-base;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.plan-limit-list {
|
||||
max-height: 65vh;
|
||||
margin-bottom: 6.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.title { @include text-base(500); }
|
||||
.plan-limit-item {
|
||||
width: 100%;
|
||||
margin-bottom: 2.4rem;
|
||||
padding: 1.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 3.2rem;
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--gray-soft-lightest);
|
||||
|
||||
span {
|
||||
@include text-xs;
|
||||
color: var(--gray-hard-light);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
@include text-base(600);
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.grpBtn {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
.plan-limit-modal {
|
||||
.grp {
|
||||
margin-bottom: 3.2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
button {
|
||||
flex: 1;
|
||||
padding: 1.6rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--gray-soft-lightest);
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
color: var(--gray-soft-darkest);
|
||||
@include text-base;
|
||||
&.is-active {
|
||||
border: 1px solid var(--gray-soft-darkest);
|
||||
background-color: var(--gray-hard-darkest);
|
||||
color: var(--gray-soft-lightest);
|
||||
}
|
||||
}
|
||||
button:first-of-type {
|
||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||
}
|
||||
button:last-of-type {
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
.plan-pricing-form {
|
||||
.fab-tabs .tabs li {
|
||||
margin-bottom: 1.6rem;
|
||||
&:hover { background-color: var(--gray-soft); }
|
||||
&.react-tabs__tab--selected:hover { background-color: transparent; }
|
||||
}
|
||||
|
||||
.react-tabs__tab-panel {
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
@ -15,8 +15,6 @@
|
||||
flex-direction: column;
|
||||
gap: 3.2rem;
|
||||
|
||||
.fab-alert { margin: 0; }
|
||||
|
||||
section { @include layout-settings; }
|
||||
.save-btn { align-self: flex-start; }
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
flex-direction: column;
|
||||
gap: 3.2rem;
|
||||
|
||||
.fab-alert { margin: 0; }
|
||||
|
||||
section { @include layout-settings; }
|
||||
.save-btn { align-self: flex-start; }
|
||||
}
|
||||
|
@ -37,6 +37,7 @@
|
||||
border-radius: var(--border-radius);
|
||||
& > * { margin-bottom: 0; }
|
||||
& > *:not(:last-child) { margin-bottom: 3.2rem; }
|
||||
.fab-alert { margin: 0 0 1.6rem; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
|
@ -164,6 +164,7 @@ en:
|
||||
group: "Group"
|
||||
transversal: "Transversal plan"
|
||||
transversal_help: "If this option is checked, a copy of this plan will be created for each currently enabled groups."
|
||||
display: "Display"
|
||||
category: "Category"
|
||||
category_help: "Categories allow you to group the subscription plans, on the public view of the subscriptions."
|
||||
number_of_periods: "Number of periods"
|
||||
@ -182,7 +183,7 @@ en:
|
||||
description: "Description"
|
||||
information_sheet: "Information sheet"
|
||||
notified_partner: "Notified partner"
|
||||
new_user: "New user ..."
|
||||
new_user: "New user"
|
||||
alert_partner_notification: "As part of a partner subscription, some notifications may be sent to this user."
|
||||
disabled: "Disable subscription"
|
||||
disabled_help: "Beware: disabling this plan won't unsubscribe users having active subscriptions with it."
|
||||
@ -194,6 +195,27 @@ en:
|
||||
save: "Save"
|
||||
create_success: "Plan(s) successfully created. Don't forget to redefine prices."
|
||||
update_success: "The plan was updated successfully"
|
||||
plan_limit_form:
|
||||
usage_limitation: "Limitation of use"
|
||||
usage_limitation_info: "Define a maximum number of reservation hours per day and per machine category. Machine categories that have no parameters configured will not be subject to any limitation."
|
||||
usage_limitation_switch: "Restrict machine reservations to a number of hours per day."
|
||||
new_usage_limitation: "Add a limitation of use"
|
||||
all_limitations: "All limitations"
|
||||
by_categories: "By machines categories"
|
||||
by_machine: "By machine"
|
||||
category: "Machines category"
|
||||
machine: "Machine name"
|
||||
max_hours_per_day: "Max. hours/day"
|
||||
plan_limit_modal:
|
||||
title: "Manage limitation of use"
|
||||
limit_reservations: "Limit reservations"
|
||||
by_categories: "By machines categories"
|
||||
by_machine: "By machine"
|
||||
category: "Machines category"
|
||||
machine: "Machine name"
|
||||
categories_info: "If you select all machine categories, the limits will apply across the board. Please note that if you have already created limitations for specific categories, these will be permanently overwritten."
|
||||
machine_info: "If you select all machines, the limits will apply across the board. Please note that if you have already created limitations for machines, these will be permanently overwritten."
|
||||
max_hours_per_day: "Maximum number of reservation hours per day"
|
||||
partner_modal:
|
||||
title: "Create a new partner"
|
||||
create_partner: "Create the partner"
|
||||
@ -2300,6 +2322,7 @@ en:
|
||||
stocks: "Stock:"
|
||||
internal: "Private stock"
|
||||
external: "Public stock"
|
||||
edit: "Edit"
|
||||
all: "All types"
|
||||
remaining_stock: "Remaining stock"
|
||||
type_in: "Add"
|
||||
|
Loading…
x
Reference in New Issue
Block a user