diff --git a/app/frontend/src/javascript/components/form/form-unsaved-list.tsx b/app/frontend/src/javascript/components/form/form-unsaved-list.tsx index 65fe050d7..e96675f60 100644 --- a/app/frontend/src/javascript/components/form/form-unsaved-list.tsx +++ b/app/frontend/src/javascript/components/form/form-unsaved-list.tsx @@ -2,7 +2,7 @@ import { FieldArrayWithId, UseFieldArrayRemove } from 'react-hook-form/dist/type import { UseFormRegister } from 'react-hook-form'; import { FieldValues } from 'react-hook-form/dist/types/fields'; import { useTranslation } from 'react-i18next'; -import { ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import { X } from 'phosphor-react'; import { FormInput } from './form-input'; import { FieldArrayPath } from 'react-hook-form/dist/types/path'; @@ -14,15 +14,18 @@ interface FormUnsavedListProps) => boolean, - formAttributeName: string, - renderFieldAttribute: (field: FieldArrayWithId, attribute: string) => ReactNode, + renderField: (field: FieldArrayWithId) => ReactNode, + formAttributeName: `${string}_attributes`, + formAttributes: Array>, + saveReminderLabel?: string | ReactNode, + cancelLabel?: string | ReactNode } /** * 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 = = FieldArrayPath, TKeyName extends string = 'id'>({ fields, remove, register, className, title, shouldRenderField, formAttributeName, renderFieldAttribute }: FormUnsavedListProps) => { +export const FormUnsavedList = = FieldArrayPath, TKeyName extends string = 'id'>({ fields, remove, register, className, title, shouldRenderField = () => true, renderField, formAttributeName, formAttributes, saveReminderLabel, cancelLabel }: FormUnsavedListProps) => { const { t } = useTranslation('shared'); /** @@ -31,25 +34,26 @@ export const FormUnsavedList = , index: number): ReactNode => { return (
- {Object.keys(field).map(attribute => ( -
- {renderFieldAttribute(field, attribute)} - -
- ))} + {renderField(field)}

remove(index)}> - {t('app.shared.form_unsaved_list.cancel')} + {cancelLabel || t('app.shared.form_unsaved_list.cancel')}

+ {formAttributes.map((attribute, attrIndex) => ( + + ))}
); }; + + if (fields.filter(shouldRenderField).length === 0) return null; + return (
{title} - {t('app.shared.form_unsaved_list.save_reminder')} + {saveReminderLabel || t('app.shared.form_unsaved_list.save_reminder')} {fields.map((field, index) => { - if (typeof shouldRenderField === 'function' && !shouldRenderField(field)) return false; + if (!shouldRenderField(field)) return false; return renderUnsavedField(field, index); }).filter(Boolean)}
diff --git a/app/frontend/src/javascript/components/plans/plan-limit-form.tsx b/app/frontend/src/javascript/components/plans/plan-limit-form.tsx index 4d32c1183..be5e1220a 100644 --- a/app/frontend/src/javascript/components/plans/plan-limit-form.tsx +++ b/app/frontend/src/javascript/components/plans/plan-limit-form.tsx @@ -3,11 +3,10 @@ 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, X } from 'phosphor-react'; +import { PencilSimple, Trash } from 'phosphor-react'; import { PlanLimitModal } from './plan-limit-modal'; import { Plan, PlanLimitation } from '../../models/plan'; import { useFieldArray, UseFormRegister } from 'react-hook-form'; -import { FormInput } from '../form/form-input'; import { Machine } from '../../models/machine'; import { MachineCategory } from '../../models/machine-category'; import MachineAPI from '../../api/machine'; @@ -56,34 +55,24 @@ export const PlanLimitForm = ({ register, control, for }; /** - * Render an attribute of an unsaved limitation of use + * Render an unsaved limitation of use */ - const renderOngoingLimitAttribute = (limit: PlanLimitation, attribute: string): ReactNode => { - switch (attribute) { - case 'limitable_id': - if (limit.limitable_type === 'MachineCategory') { - return ( - <> - {t('app.admin.plan_limit_form.category')} -

{categories?.find(c => c.id === limit.limitable_id)?.name}

- - ); - } - return ( - <> - {t('app.admin.plan_limit_form.machine')} -

{machines?.find(m => m.id === limit.limitable_id)?.name}

- - ); - case 'limit': - return ( - <> - {t('app.admin.plan_limit_form.max_hours_per_day')} -

{limit.limit}

- - ); - } - }; + const renderOngoingLimit = (limit: PlanLimitation): ReactNode => ( + <> + {(limit.limitable_type === 'MachineCategory' &&
+ {t('app.admin.plan_limit_form.category')} +

{categories?.find(c => c.id === limit.limitable_id)?.name}

+
) || +
+ {t('app.admin.plan_limit_form.machine')} +

{machines?.find(m => m.id === limit.limitable_id)?.name}

+
} +
+ {t('app.admin.plan_limit_form.max_hours_per_day')} +

{limit.limit}

+
+ + ); return (
@@ -142,10 +131,12 @@ export const PlanLimitForm = ({ register, control, for limit.limitable_type === 'MachineCategory'} formAttributeName="plan_limitations_attributes" - renderFieldAttribute={renderOngoingLimitAttribute} /> + formAttributes={['limitable_id', 'limit']} + renderField={renderOngoingLimit} + cancelLabel={t('app.admin.plan_limit_form.cancel')} />

{t('app.admin.plan_limit_form.by_machine')}

@@ -181,7 +172,10 @@ export const PlanLimitForm = ({ register, control, for title={t('app.admin.plan_limit_form.ongoing_limit')} shouldRenderField={(limit: PlanLimitation) => limit.limitable_type === 'Machine'} formAttributeName="plan_limitations_attributes" - renderFieldAttribute={renderOngoingLimitAttribute} /> + formAttributes={['limitable_id', 'limit']} + renderField={renderOngoingLimit} + saveReminderLabel={t('app.admin.plan_limit_form.save_reminder')} + cancelLabel={t('app.admin.plan_limit_form.cancel')} /> { currentFormValues: Product, @@ -159,6 +160,25 @@ export const ProductStockForm = ({ currentFormValues, } }; + /** + * Render an attribute of an unsaved stock movement + */ + const renderOngoingStockMovement = (movement: ProductStockMovement): ReactNode => ( + <> +
+

{t(`app.admin.store.product_stock_form.type_${ProductLib.stockMovementType(movement.reason)}`)}

+
+
+ {t(`app.admin.store.product_stock_form.${movement.stock_type}`)} +

{ProductLib.absoluteStockMovement(movement.quantity, movement.reason)}

+
+
+ {t('app.admin.store.product_stock_form.reason')} +

{t(ProductLib.stockMovementReasonTrKey(movement.reason))}

+
+ + ); + return (

{t('app.admin.store.product_stock_form.stock_up_to_date')}  @@ -181,33 +201,16 @@ export const ProductStockForm = ({ currentFormValues, } className="is-black">{t('app.admin.store.product_stock_form.edit')}

- {fields.length > 0 &&
- {t('app.admin.store.product_stock_form.ongoing_operations')} - {t('app.admin.store.product_stock_form.save_reminder')} - {fields.map((newMovement, index) => ( -
-
-

{t(`app.admin.store.product_stock_form.type_${ProductLib.stockMovementType(newMovement.reason)}`)}

-
-
- {t(`app.admin.store.product_stock_form.${newMovement.stock_type}`)} -

{ProductLib.absoluteStockMovement(newMovement.quantity, newMovement.reason)}

-
-
- {t('app.admin.store.product_stock_form.reason')} -

{t(ProductLib.stockMovementReasonTrKey(newMovement.reason))}

-
-

remove(index)}> - {t('app.admin.store.product_stock_form.cancel')} - -

- - - -
- ))} -
} +
diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index f89015592..b70e769c6 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -62,6 +62,7 @@ @import "modules/form/form-checklist"; @import "modules/form/form-file-upload"; @import "modules/form/form-image-upload"; +@import "modules/form/form-unsaved-list"; @import "modules/group/change-group"; @import "modules/invoices/invoices-settings-panel"; @import "modules/invoices/vat-settings-modal"; diff --git a/app/frontend/src/stylesheets/modules/form/form-unsaved-list.scss b/app/frontend/src/stylesheets/modules/form/form-unsaved-list.scss new file mode 100644 index 000000000..f7b1a37dc --- /dev/null +++ b/app/frontend/src/stylesheets/modules/form/form-unsaved-list.scss @@ -0,0 +1,47 @@ +.form-unsaved-list { + .save-notice { + @include text-xs; + margin-left: 1rem; + color: var(--alert); + } + .unsaved-field { + background-color: var(--gray-soft-light); + border: 0; + padding: 1.2rem; + margin-top: 1rem;width: 100%; + display: flex; + gap: 4.8rem; + justify-items: flex-start; + align-items: center; + border-radius: var(--border-radius); + + & > * { flex: 1 1 45%; } + + p { + margin: 0; + @include text-base; + } + .title { + @include text-base(600); + flex: 1 1 100%; + } + .group { + span { + @include text-xs; + color: var(--gray-hard-light); + } + p { @include text-base(600); } + } + + .cancel-action { + &:hover { + text-decoration: underline; + cursor: pointer; + } + svg { + margin-left: 1rem; + vertical-align: middle; + } + } + } +} diff --git a/app/frontend/src/stylesheets/modules/plans/plan-limit-form.scss b/app/frontend/src/stylesheets/modules/plans/plan-limit-form.scss index e0e65746c..273f8093b 100644 --- a/app/frontend/src/stylesheets/modules/plans/plan-limit-form.scss +++ b/app/frontend/src/stylesheets/modules/plans/plan-limit-form.scss @@ -5,32 +5,6 @@ section { @include layout-settings; } - .ongoing-limits { - margin: 2.4rem 0; - .save-notice { - @include text-xs; - margin-left: 1rem; - color: var(--alert); - } - .unsaved-plan-limit { - background-color: var(--gray-soft-light); - border: 0; - padding: 1.2rem; - margin-top: 1rem; - - .cancel-action { - &:hover { - text-decoration: underline; - cursor: pointer; - } - svg { - margin-left: 1rem; - vertical-align: middle; - } - } - } - } - .plan-limit-grp { header { @include header(); @@ -39,9 +13,11 @@ margin: 0; } } + .form-unsaved-list { + margin-bottom: 6.4rem; + } .plan-limit-list { max-height: 65vh; - margin-bottom: 6.4rem; display: flex; flex-direction: column; overflow-y: auto; diff --git a/app/frontend/src/stylesheets/modules/store/product-stock-form.scss b/app/frontend/src/stylesheets/modules/store/product-stock-form.scss index 5290c7a80..1e9a2bd78 100644 --- a/app/frontend/src/stylesheets/modules/store/product-stock-form.scss +++ b/app/frontend/src/stylesheets/modules/store/product-stock-form.scss @@ -3,28 +3,6 @@ .ongoing-stocks { margin: 2.4rem 0; - .save-notice { - @include text-xs; - margin-left: 1rem; - color: var(--alert); - } - .unsaved-stock-movement { - background-color: var(--gray-soft-light); - border: 0; - padding: 1.2rem; - margin-top: 1rem; - - .cancel-action { - &:hover { - text-decoration: underline; - cursor: pointer; - } - svg { - margin-left: 1rem; - vertical-align: middle; - } - } - } } .store-list { margin-top: 2.4rem; diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index fbf3d0ac5..c523ca492 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -206,8 +206,7 @@ en: category: "Machines category" machine: "Machine name" max_hours_per_day: "Max. hours/day" - ongoing_limit: "Ongoing settings" - save_reminder: "Don't forget to save your settings" + ongoing_limitations: "Ongoing limitations" cancel: "Cancel this limitation" plan_limit_modal: title: "Manage limitation of use" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 18a3da175..92ecc9b37 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -540,3 +540,6 @@ en: show_reserved_uniq: "Show only slots with reservations" machine: machine_uncategorized: "Uncategorized machines" + form_unsaved_list: + save_reminder: "Do not forget to save your changes" + cancel: "Cancel"