mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
(feat) update limitations
This commit is contained in:
parent
622a14909a
commit
6abea03182
12
app/controllers/api/plan_limitations_controller.rb
Normal file
12
app/controllers/api/plan_limitations_controller.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API Controller for resources of type PlanLimitation
|
||||
# PlanLimitation allows to restrict bookings of resources for the subscribers of that plan.
|
||||
class PlanLimitationsController < API::ApiController
|
||||
def destroy
|
||||
@limitation = PlanLimitation.find(params[:id])
|
||||
authorize @limitation
|
||||
@limitation.destroy
|
||||
head :no_content
|
||||
end
|
||||
end
|
@ -85,7 +85,7 @@ class API::PlansController < API::ApiController
|
||||
plan_file_attributes: %i[id attachment _destroy],
|
||||
prices_attributes: %i[id amount],
|
||||
advanced_accounting_attributes: %i[code analytical_section],
|
||||
plan_limitations_attributes: %i[id limitable_id limitable_type limit])
|
||||
plan_limitations_attributes: %i[id limitable_id limitable_type limit _destroy])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
9
app/frontend/src/javascript/api/plan-limitation.ts
Normal file
9
app/frontend/src/javascript/api/plan-limitation.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
export default class PlanLimitationAPI {
|
||||
static async destroy (id: number): Promise<void> {
|
||||
const res: AxiosResponse<void> = await apiClient.delete(`/api/plan_limitations/${id}`);
|
||||
return res?.data;
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ interface EditDestroyButtonsProps {
|
||||
apiDestroy: (itemId: number) => Promise<void>,
|
||||
confirmationMessage?: string|ReactNode,
|
||||
className?: string,
|
||||
iconSize?: number
|
||||
iconSize?: number,
|
||||
showEditButton?: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -22,7 +23,7 @@ interface EditDestroyButtonsProps {
|
||||
* 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> = ({ onDeleteSuccess, onError, onEdit, itemId, itemType, apiDestroy, confirmationMessage, className, iconSize = 20 }) => {
|
||||
export const EditDestroyButtons: React.FC<EditDestroyButtonsProps> = ({ onDeleteSuccess, onError, onEdit, itemId, itemType, apiDestroy, confirmationMessage, className, iconSize = 20, showEditButton = true }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [deletionModal, setDeletionModal] = useState<boolean>(false);
|
||||
@ -50,9 +51,9 @@ export const EditDestroyButtons: React.FC<EditDestroyButtonsProps> = ({ onDelete
|
||||
return (
|
||||
<>
|
||||
<div className={`edit-destroy-buttons ${className || ''}`}>
|
||||
<FabButton className='edit-btn' onClick={onEdit}>
|
||||
{showEditButton && <FabButton className='edit-btn' onClick={onEdit}>
|
||||
<PencilSimple size={iconSize} weight="fill" />
|
||||
</FabButton>
|
||||
</FabButton>}
|
||||
<FabButton type='button' className='delete-btn' onClick={toggleDeletionModal}>
|
||||
<Trash size={iconSize} weight="fill" />
|
||||
</FabButton>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FieldArrayWithId, UseFieldArrayRemove } from 'react-hook-form/dist/types/fieldArray';
|
||||
import { FieldArrayWithId } from 'react-hook-form/dist/types/fieldArray';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -9,7 +9,7 @@ import { FieldArrayPath } from 'react-hook-form/dist/types/path';
|
||||
|
||||
interface FormUnsavedListProps<TFieldValues, TFieldArrayName extends FieldArrayPath<TFieldValues>, TKeyName extends string> {
|
||||
fields: Array<FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>>,
|
||||
remove: UseFieldArrayRemove,
|
||||
onRemove?: (index: number) => void,
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
className?: string,
|
||||
title: string,
|
||||
@ -25,7 +25,7 @@ interface FormUnsavedListProps<TFieldValues, TFieldArrayName extends FieldArrayP
|
||||
* This component render a list of unsaved attributes, created elsewhere than in the form (e.g. in a modal dialog)
|
||||
* and pending for the form to be saved.
|
||||
*/
|
||||
export const FormUnsavedList = <TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>, TKeyName extends string = 'id'>({ fields, remove, register, className, title, shouldRenderField = () => true, renderField, formAttributeName, formAttributes, saveReminderLabel, cancelLabel }: FormUnsavedListProps<TFieldValues, TFieldArrayName, TKeyName>) => {
|
||||
export const FormUnsavedList = <TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>, TKeyName extends string = 'id'>({ fields, onRemove, register, className, title, shouldRenderField = () => true, renderField, formAttributeName, formAttributes, saveReminderLabel, cancelLabel }: FormUnsavedListProps<TFieldValues, TFieldArrayName, TKeyName>) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
/**
|
||||
@ -35,7 +35,7 @@ export const FormUnsavedList = <TFieldValues extends FieldValues = FieldValues,
|
||||
return (
|
||||
<div key={index} className="unsaved-field">
|
||||
{renderField(field)}
|
||||
<p className="cancel-action" onClick={() => remove(index)}>
|
||||
<p className="cancel-action" onClick={() => onRemove(index)}>
|
||||
{cancelLabel || t('app.shared.form_unsaved_list.cancel')}
|
||||
<X size={20} />
|
||||
</p>
|
||||
|
@ -44,7 +44,7 @@ interface PlanFormProps {
|
||||
* Form to edit or create subscription plans
|
||||
*/
|
||||
export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuccess, beforeSubmit, uiRouter }) => {
|
||||
const { handleSubmit, register, control, formState, setValue } = useForm<Plan>({ defaultValues: { ...plan } });
|
||||
const { handleSubmit, register, control, formState, setValue, getValues, resetField } = useForm<Plan>({ defaultValues: { ...plan } });
|
||||
const output = useWatch<Plan>({ control }); // eslint-disable-line
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
@ -332,7 +332,10 @@ export const PlanForm: React.FC<PlanFormProps> = ({ action, plan, onError, onSuc
|
||||
content: <PlanLimitForm control={control}
|
||||
register={register}
|
||||
formState={formState}
|
||||
onError={onError} />
|
||||
onError={onError}
|
||||
onSuccess={onSuccess}
|
||||
getValues={getValues}
|
||||
resetField={resetField} />
|
||||
}
|
||||
]} />
|
||||
</form>
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { Control, FormState, UseFormGetValues, UseFormResetField } 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';
|
||||
import { Plan, PlanLimitation } from '../../models/plan';
|
||||
import { useFieldArray, UseFormRegister, useWatch } from 'react-hook-form';
|
||||
@ -12,25 +11,31 @@ import { MachineCategory } from '../../models/machine-category';
|
||||
import MachineAPI from '../../api/machine';
|
||||
import MachineCategoryAPI from '../../api/machine-category';
|
||||
import { FormUnsavedList } from '../form/form-unsaved-list';
|
||||
import { EditDestroyButtons } from '../base/edit-destroy-buttons';
|
||||
import PlanLimitationAPI from '../../api/plan-limitation';
|
||||
|
||||
interface PlanLimitFormProps<TContext extends object> {
|
||||
register: UseFormRegister<Plan>,
|
||||
control: Control<Plan, TContext>,
|
||||
formState: FormState<Plan>,
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
getValues: UseFormGetValues<Plan>,
|
||||
resetField: UseFormResetField<Plan>
|
||||
}
|
||||
|
||||
/**
|
||||
* Form tab to manage a subscription's usage limit
|
||||
*/
|
||||
export const PlanLimitForm = <TContext extends object> ({ register, control, formState, onError }: PlanLimitFormProps<TContext>) => {
|
||||
export const PlanLimitForm = <TContext extends object> ({ register, control, formState, onError, onSuccess, getValues, resetField }: PlanLimitFormProps<TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const { fields, append, remove } = useFieldArray<Plan, 'plan_limitations_attributes'>({ control, name: 'plan_limitations_attributes' });
|
||||
const { fields, append, remove, update } = useFieldArray<Plan, 'plan_limitations_attributes'>({ control, name: 'plan_limitations_attributes' });
|
||||
const limiting = useWatch<Plan>({ control, name: 'limiting' });
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [machines, setMachines] = useState<Array<Machine>>([]);
|
||||
const [categories, setCategories] = useState<Array<MachineCategory>>([]);
|
||||
const [edited, setEdited] = useState<{index: number, limitation: PlanLimitation}>(null);
|
||||
|
||||
useEffect(() => {
|
||||
MachineAPI.index({ disabled: false })
|
||||
@ -51,8 +56,49 @@ export const PlanLimitForm = <TContext extends object> ({ register, control, for
|
||||
/**
|
||||
* Triggered when a new limit was added or an existing limit was modified
|
||||
*/
|
||||
const onPlanLimitSuccess = (planLimit: PlanLimitation): void => {
|
||||
append({ ...planLimit });
|
||||
const onLimitationSuccess = (limitation: PlanLimitation): void => {
|
||||
const id = getValues(`plan_limitations_attributes.${edited?.index}.id`);
|
||||
if (id) {
|
||||
update(edited.index, { ...limitation, id });
|
||||
setEdited(null);
|
||||
} else {
|
||||
append({ ...limitation });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggered when an unsaved limit was removed from the "pending" list.
|
||||
*/
|
||||
const onRemoveUnsaved = (index: number): void => {
|
||||
const id = getValues(`plan_limitations_attributes.${index}.id`);
|
||||
if (id) {
|
||||
// will reset the field to its default values
|
||||
resetField(`plan_limitations_attributes.${index}`);
|
||||
// unmount and remount the field
|
||||
update(index, getValues(`plan_limitations_attributes.${index}`));
|
||||
} else {
|
||||
remove(index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the limitation was deleted. Return a callback accepting a message
|
||||
*/
|
||||
const onLimitationDeleted = (index: number): (message: string) => void => {
|
||||
return (message: string) => {
|
||||
onSuccess(message);
|
||||
remove(index);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user wants to modify a limitation. Return a callback
|
||||
*/
|
||||
const onEditLimitation = (limitation: PlanLimitation, index: number): () => void => {
|
||||
return () => {
|
||||
setEdited({ index, limitation });
|
||||
toggleModal();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -100,84 +146,83 @@ export const PlanLimitForm = <TContext extends object> ({ register, control, for
|
||||
</FabButton>
|
||||
</div>
|
||||
</header>
|
||||
<FormUnsavedList fields={fields}
|
||||
onRemove={onRemoveUnsaved}
|
||||
register={register}
|
||||
title={t('app.admin.plan_limit_form.ongoing_limitations')}
|
||||
shouldRenderField={(limit: PlanLimitation) => limit._modified}
|
||||
formAttributeName="plan_limitations_attributes"
|
||||
formAttributes={['id', 'limitable_type', 'limitable_id', 'limit']}
|
||||
renderField={renderOngoingLimit}
|
||||
cancelLabel={t('app.admin.plan_limit_form.cancel')} />
|
||||
|
||||
{fields.filter(f => f.limitable_type === 'MachineCategory').length > 0 &&
|
||||
{fields.filter(f => f._modified).length > 0 &&
|
||||
<p className="title">{t('app.admin.plan_limit_form.saved_limitations')}</p>
|
||||
}
|
||||
|
||||
{fields.filter(f => f.limitable_type === 'MachineCategory' && !f._modified).length > 0 &&
|
||||
<div className='plan-limit-list'>
|
||||
<p className="title">{t('app.admin.plan_limit_form.by_categories')}</p>
|
||||
{fields.filter(f => f.limitable_type === 'MachineCategory' && !f.modified).map(limitation => (
|
||||
<div className="plan-limit-item" key={limitation.id}>
|
||||
<div className="grp">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.category')}</span>
|
||||
<p>{categories.find(c => c.id === limitation.limitable_id)?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>{limitation.limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
{fields.map((limitation, index) => {
|
||||
if (limitation.limitable_type !== 'MachineCategory' || limitation._modified) return false;
|
||||
|
||||
<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>
|
||||
return (
|
||||
<div className="plan-limit-item" key={limitation.id}>
|
||||
<div className="grp">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.category')}</span>
|
||||
<p>{categories.find(c => c.id === limitation.limitable_id)?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>{limitation.limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='actions'>
|
||||
<EditDestroyButtons onDeleteSuccess={onLimitationDeleted(index)}
|
||||
onError={onError}
|
||||
onEdit={onEditLimitation(limitation, index)}
|
||||
itemId={limitation.id}
|
||||
itemType={t('app.admin.plan_limit_form.limitation')}
|
||||
apiDestroy={PlanLimitationAPI.destroy} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<FormUnsavedList fields={fields}
|
||||
remove={remove}
|
||||
register={register}
|
||||
title={t('app.admin.plan_limit_form.ongoing_limitations')}
|
||||
shouldRenderField={(limit: PlanLimitation) => limit.limitable_type === 'MachineCategory' && limit.modified}
|
||||
formAttributeName="plan_limitations_attributes"
|
||||
formAttributes={['id', 'limitable_type', 'limitable_id', 'limit']}
|
||||
renderField={renderOngoingLimit}
|
||||
cancelLabel={t('app.admin.plan_limit_form.cancel')} />
|
||||
);
|
||||
}).filter(Boolean)}
|
||||
</div>
|
||||
}
|
||||
|
||||
{fields.filter(f => f.limitable_type === 'Machine').length > 0 &&
|
||||
{fields.filter(f => f.limitable_type === 'Machine' && !f._modified).length > 0 &&
|
||||
<div className='plan-limit-list'>
|
||||
<p className="title">{t('app.admin.plan_limit_form.by_machine')}</p>
|
||||
{fields.filter(f => f.limitable_type === 'Machine' && !f.modified).map(limitation => (
|
||||
<div className="plan-limit-item" key={limitation.id}>
|
||||
<div className="grp">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.machine')}</span>
|
||||
<p>{machines.find(m => m.id === limitation.limitable_id)?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>{limitation.limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
{fields.map((limitation, index) => {
|
||||
if (limitation.limitable_type !== 'Machine' || limitation._modified) return false;
|
||||
|
||||
<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>
|
||||
return (
|
||||
<div className="plan-limit-item" key={limitation.id}>
|
||||
<div className="grp">
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.machine')}</span>
|
||||
<p>{machines.find(m => m.id === limitation.limitable_id)?.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('app.admin.plan_limit_form.max_hours_per_day')}</span>
|
||||
<p>{limitation.limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='actions'>
|
||||
<EditDestroyButtons onDeleteSuccess={onLimitationDeleted(index)}
|
||||
onError={onError}
|
||||
onEdit={onEditLimitation(limitation, index)}
|
||||
itemId={limitation.id}
|
||||
itemType={t('app.admin.plan_limit_form.limitation')}
|
||||
apiDestroy={PlanLimitationAPI.destroy} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<FormUnsavedList fields={fields}
|
||||
remove={remove}
|
||||
register={register}
|
||||
title={t('app.admin.plan_limit_form.ongoing_limitations')}
|
||||
shouldRenderField={(limit: PlanLimitation) => limit.limitable_type === 'Machine' && limit.modified}
|
||||
formAttributeName="plan_limitations_attributes"
|
||||
formAttributes={['id', 'limitable_type', 'limitable_id', 'limit']}
|
||||
renderField={renderOngoingLimit}
|
||||
cancelLabel={t('app.admin.plan_limit_form.cancel')} />
|
||||
);
|
||||
}).filter(Boolean)}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
@ -186,7 +231,8 @@ export const PlanLimitForm = <TContext extends object> ({ register, control, for
|
||||
machines={machines}
|
||||
categories={categories}
|
||||
toggleModal={toggleModal}
|
||||
onSuccess={onPlanLimitSuccess} />
|
||||
onSuccess={onLimitationSuccess}
|
||||
limitation={edited?.limitation} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -10,24 +10,30 @@ import { Machine } from '../../models/machine';
|
||||
import { MachineCategory } from '../../models/machine-category';
|
||||
import { SelectOption } from '../../models/select';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface PlanLimitModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
onSuccess: (limit: PlanLimitation) => void,
|
||||
machines: Array<Machine>
|
||||
categories: Array<MachineCategory>
|
||||
categories: Array<MachineCategory>,
|
||||
limitation?: PlanLimitation,
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to manage subscriptions limitations of use
|
||||
*/
|
||||
export const PlanLimitModal: React.FC<PlanLimitModalProps> = ({ isOpen, toggleModal, machines, categories, onSuccess }) => {
|
||||
export const PlanLimitModal: React.FC<PlanLimitModalProps> = ({ isOpen, toggleModal, machines, categories, onSuccess, limitation }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { register, control, formState, setValue, handleSubmit } = useForm<PlanLimitation>({ defaultValues: { limitable_type: 'MachineCategory' } });
|
||||
const { register, control, formState, setValue, handleSubmit, reset } = useForm<PlanLimitation>({ defaultValues: limitation || { limitable_type: 'MachineCategory' } });
|
||||
const limitType = useWatch({ control, name: 'limitable_type' });
|
||||
|
||||
useEffect(() => {
|
||||
reset(limitation);
|
||||
}, [limitation]);
|
||||
|
||||
/**
|
||||
* Toggle the form between 'categories' and 'machine'
|
||||
*/
|
||||
@ -47,7 +53,8 @@ export const PlanLimitModal: React.FC<PlanLimitModalProps> = ({ isOpen, toggleMo
|
||||
event.preventDefault();
|
||||
}
|
||||
return handleSubmit((data: PlanLimitation) => {
|
||||
onSuccess({ ...data, modified: true });
|
||||
onSuccess({ ...data, _modified: true });
|
||||
reset({});
|
||||
toggleModal();
|
||||
})(event);
|
||||
};
|
||||
@ -85,6 +92,7 @@ export const PlanLimitModal: React.FC<PlanLimitModalProps> = ({ isOpen, toggleMo
|
||||
</button>
|
||||
</div>
|
||||
<FabAlert level='info'>{t('app.admin.plan_limit_modal.machine_info')}</FabAlert>
|
||||
<FormInput register={register} id="id" type="hidden" />
|
||||
<FormInput register={register} id="limitable_type" type="hidden" />
|
||||
<FormSelect options={buildOptions()}
|
||||
control={control}
|
||||
|
@ -203,7 +203,7 @@ export const ProductStockForm = <TContext extends object> ({ currentFormValues,
|
||||
|
||||
<FormUnsavedList fields={fields}
|
||||
className="ongoing-stocks"
|
||||
remove={remove}
|
||||
onRemove={remove}
|
||||
register={register}
|
||||
title={t('app.admin.store.product_stock_form.ongoing_operations')}
|
||||
formAttributeName="product_stock_movements_attributes"
|
||||
|
@ -18,7 +18,8 @@ export interface PlanLimitation {
|
||||
limitable_id: number,
|
||||
limitable_type: LimitableType,
|
||||
limit: number,
|
||||
modified?: boolean,
|
||||
_modified?: boolean,
|
||||
_destroy?: boolean,
|
||||
}
|
||||
|
||||
export interface Plan {
|
||||
|
@ -46,19 +46,6 @@
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 540px) {
|
||||
|
@ -9,5 +9,5 @@ class PlanLimitation < ApplicationRecord
|
||||
belongs_to :machine_category, foreign_type: 'MachineCategory', foreign_key: 'limitable_id', inverse_of: :plan_limitations
|
||||
|
||||
validates :limitable_id, :limitable_type, :limit, :plan_id, presence: true
|
||||
validates :plan_id, :limitable_id, :limitable_type, uniqueness: true
|
||||
validates :limitable_id, uniqueness: { scope: %i[limitable_type plan_id] }
|
||||
end
|
||||
|
8
app/policies/plan_limitation_policy.rb
Normal file
8
app/policies/plan_limitation_policy.rb
Normal file
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::PlanLimitationsController
|
||||
class PlanLimitationPolicy < ApplicationPolicy
|
||||
def destroy?
|
||||
user.admin?
|
||||
end
|
||||
end
|
@ -207,7 +207,9 @@ en:
|
||||
machine: "Machine name"
|
||||
max_hours_per_day: "Max. hours/day"
|
||||
ongoing_limitations: "Ongoing limitations"
|
||||
saved_limitations: "Saved limitations"
|
||||
cancel: "Cancel this limitation"
|
||||
limitation: "Limitation"
|
||||
plan_limit_modal:
|
||||
title: "Manage limitation of use"
|
||||
limit_reservations: "Limit reservations"
|
||||
|
@ -116,6 +116,7 @@ Rails.application.routes.draw do
|
||||
patch 'cancel', on: :member
|
||||
end
|
||||
resources :plan_categories
|
||||
resources :plan_limitations, only: [:destroy]
|
||||
resources :plans do
|
||||
get 'durations', on: :collection
|
||||
end
|
||||
|
@ -161,7 +161,7 @@
|
||||
"react-cool-onclickoutside": "^1.7.0",
|
||||
"react-custom-events": "^1.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hook-form": "^7.30.0",
|
||||
"react-hook-form": "^7.31.3",
|
||||
"react-i18next": "^11.15.6",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-select": "^5.3.2",
|
||||
|
@ -9062,10 +9062,10 @@ react-dom@^17.0.2:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-hook-form@^7.30.0:
|
||||
version "7.30.0"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.30.0.tgz#c9e2fd54d3627e43bd94bf38ef549df2e80c1371"
|
||||
integrity sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ==
|
||||
react-hook-form@^7.31.3:
|
||||
version "7.43.5"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.5.tgz#b320405594f1506d8d57b954383166d4ff563778"
|
||||
integrity sha512-YcaXhuFHoOPipu5pC7ckxrLrialiOcU91pKu8P+isAcXZyMgByUK9PkI9j5fENO4+6XU5PwWXRGMIFlk9u9UBQ==
|
||||
|
||||
react-i18next@^11.15.6:
|
||||
version "11.15.6"
|
||||
|
Loading…
x
Reference in New Issue
Block a user