diff --git a/app/controllers/api/prices_controller.rb b/app/controllers/api/prices_controller.rb index de84774a2..fdcf6de1b 100644 --- a/app/controllers/api/prices_controller.rb +++ b/app/controllers/api/prices_controller.rb @@ -36,7 +36,7 @@ class API::PricesController < API::ApiController def destroy authorize @price - @price.destroy + @price.safe_destroy head :no_content end diff --git a/app/frontend/src/javascript/api/space.ts b/app/frontend/src/javascript/api/space.ts index 5633bd658..6f1d9c9b9 100644 --- a/app/frontend/src/javascript/api/space.ts +++ b/app/frontend/src/javascript/api/space.ts @@ -12,4 +12,5 @@ export default class SpaceAPI { const res: AxiosResponse = await apiClient.get(`/api/spaces/${id}`); return res?.data; } + } diff --git a/app/frontend/src/javascript/components/pricing/machines/configure-packs-button.tsx b/app/frontend/src/javascript/components/pricing/machines/configure-packs-button.tsx index c5b1ff43c..ec3cbd2be 100644 --- a/app/frontend/src/javascript/components/pricing/machines/configure-packs-button.tsx +++ b/app/frontend/src/javascript/components/pricing/machines/configure-packs-button.tsx @@ -64,8 +64,8 @@ export const ConfigurePacksButton: React.FC = ({ pack }; return ( -
- {showList && @@ -73,7 +73,7 @@ export const ConfigurePacksButton: React.FC = ({ pack {packs?.map(p =>
  • {formatDuration(p.minutes)} - {FormatLib.price(p.amount)} - + diff --git a/app/frontend/src/javascript/components/pricing/machines/delete-pack.tsx b/app/frontend/src/javascript/components/pricing/machines/delete-pack.tsx index dc1d01089..bfd8a40a0 100644 --- a/app/frontend/src/javascript/components/pricing/machines/delete-pack.tsx +++ b/app/frontend/src/javascript/components/pricing/machines/delete-pack.tsx @@ -42,8 +42,8 @@ const DeletePackComponent: React.FC = ({ onSuccess, onError, pa }; return ( -
    - } onClick={toggleDeletionModal} /> +
    + } onClick={toggleDeletionModal} /> = ({ pack, onSuccess, onError }) }; return ( -
    - } onClick={handleRequestEdit} /> +
    + } onClick={handleRequestEdit} /> - {packData && } + onConfirmSendFormId="edit-pack"> + {packData && }
    ); diff --git a/app/frontend/src/javascript/components/pricing/machines/machines-pricing.tsx b/app/frontend/src/javascript/components/pricing/machines/machines-pricing.tsx index 5b3618b7d..80334aab0 100644 --- a/app/frontend/src/javascript/components/pricing/machines/machines-pricing.tsx +++ b/app/frontend/src/javascript/components/pricing/machines/machines-pricing.tsx @@ -107,7 +107,7 @@ const MachinesPricing: React.FC = ({ onError, onSuccess }) }; return ( -
    +

    diff --git a/app/frontend/src/javascript/components/pricing/machines/pack-form.tsx b/app/frontend/src/javascript/components/pricing/machines/pack-form.tsx index 831e0d899..ba4381c46 100644 --- a/app/frontend/src/javascript/components/pricing/machines/pack-form.tsx +++ b/app/frontend/src/javascript/components/pricing/machines/pack-form.tsx @@ -103,7 +103,7 @@ export const PackForm: React.FC = ({ formId, onSubmit, pack }) => }; return ( -
    + (false); /** - * Open/closes the popover listing the existing extended prices + * Return the number of minutes, user-friendly formatted + */ + const formatDuration = (minutes: number): string => { + return t('app.admin.configure_extended_prices_button.extended_price_DURATION', { DURATION: minutes }); + }; + + /** + * Open/closes the popover listing the existing packs */ const toggleShowList = (): void => { setShowList(!showList); @@ -57,22 +64,22 @@ export const ConfigureExtendedPriceButton: React.FC - - {showList && + {showList &&
      {extendedPrices?.map(extendedPrice =>
    • - {extendedPrice.duration} {t('app.admin.calendar.minutes')} - {FormatLib.price(extendedPrice.amount)} - + {formatDuration(extendedPrice.duration)} - {FormatLib.price(extendedPrice.amount)} +
    • )}
    - {extendedPrices?.length === 0 && {t('app.admin.configure_extendedPrices_button.no_extendedPrices')}} + {extendedPrices?.length === 0 && {t('app.admin.configure_extended_prices_button.no_extended_prices')}}
    }
    ); diff --git a/app/frontend/src/javascript/components/pricing/spaces/create-extended-price.tsx b/app/frontend/src/javascript/components/pricing/spaces/create-extended-price.tsx index c01be253a..082cab8ad 100644 --- a/app/frontend/src/javascript/components/pricing/spaces/create-extended-price.tsx +++ b/app/frontend/src/javascript/components/pricing/spaces/create-extended-price.tsx @@ -43,24 +43,24 @@ export const CreateExtendedPrice: React.FC = ({ onSucc // create it on the API PriceAPI.create(newExtendedPrice) .then(() => { - onSuccess(t('app.admin.create_extendedPrice.extendedPrice_successfully_created')); + onSuccess(t('app.admin.create_extended_price.extended_price_successfully_created')); toggleModal(); }) .catch(error => onError(error)); }; return ( -
    - +
    + - {t('app.admin.create_extendedPrice.new_extendedPrice_info', { TYPE: priceableType })} + {t('app.admin.create_extended_price.new_extended_price_info', { TYPE: priceableType })} diff --git a/app/frontend/src/javascript/components/pricing/spaces/delete-extended-price.tsx b/app/frontend/src/javascript/components/pricing/spaces/delete-extended-price.tsx index 56af8784e..75931d2b5 100644 --- a/app/frontend/src/javascript/components/pricing/spaces/delete-extended-price.tsx +++ b/app/frontend/src/javascript/components/pricing/spaces/delete-extended-price.tsx @@ -33,23 +33,23 @@ export const DeleteExtendedPrice: React.FC = ({ onSucc */ const onDeleteConfirmed = (): void => { PriceAPI.destroy(price.id).then(() => { - onSuccess(t('app.admin.delete_extendedPrice.extendedPrice_deleted')); + onSuccess(t('app.admin.delete_extended_price.extended_price_deleted')); }).catch((error) => { - onError(t('app.admin.delete_extendedPrice.unable_to_delete') + error); + onError(t('app.admin.delete_extended_price.unable_to_delete') + error); }); toggleDeletionModal(); }; return ( -
    - } onClick={toggleDeletionModal} /> - + } onClick={toggleDeletionModal} /> + - {t('app.admin.delete_extendedPrice.delete_confirmation')} + {t('app.admin.delete_extended_price.delete_confirmation')}
    ); diff --git a/app/frontend/src/javascript/components/pricing/spaces/edit-extended-price.tsx b/app/frontend/src/javascript/components/pricing/spaces/edit-extended-price.tsx index 994432850..3b1608a29 100644 --- a/app/frontend/src/javascript/components/pricing/spaces/edit-extended-price.tsx +++ b/app/frontend/src/javascript/components/pricing/spaces/edit-extended-price.tsx @@ -42,7 +42,7 @@ export const EditExtendedPrice: React.FC = ({ price, onS const handleUpdate = (price: Price): void => { PriceAPI.update(price) .then(() => { - onSuccess(t('app.admin.edit_extendedPrice.extendedPrice_successfully_updated')); + onSuccess(t('app.admin.edit_extended_price.extended_price_successfully_updated')); setExtendedPriceData(price); toggleModal(); }) @@ -50,15 +50,16 @@ export const EditExtendedPrice: React.FC = ({ price, onS }; return ( -
    - } onClick={handleRequestEdit} /> +
    + } onClick={handleRequestEdit} /> - {extendedPriceData && } + confirmButton={t('app.admin.edit_extended_price.confirm_changes')} + onConfirmSendFormId="edit-extended-price"> + {extendedPriceData && }
    ); diff --git a/app/frontend/src/javascript/components/pricing/spaces/extended-price-form.tsx b/app/frontend/src/javascript/components/pricing/spaces/extended-price-form.tsx index 31445ee81..eff0bcc45 100644 --- a/app/frontend/src/javascript/components/pricing/spaces/extended-price-form.tsx +++ b/app/frontend/src/javascript/components/pricing/spaces/extended-price-form.tsx @@ -9,7 +9,7 @@ declare let Fablab: IFablab; interface ExtendedPriceFormProps { formId: string, - onSubmit: (pack: Price) => void, + onSubmit: (price: Price) => void, price?: Price, } @@ -49,8 +49,8 @@ export const ExtendedPriceForm: React.FC = ({ formId, on }; return ( - - + + = ({ onError, onSuccess }) => }; return ( -
    +
    -

    -

    -

    {t('app.admin.pricing.you_can_override')}

    +

    +

    +

    {t('app.admin.spaces_pricing.you_can_override')}

    +

    {t('app.admin.spaces_pricing.extended_prices')}

    - + {groups?.map(group => )} diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 32d9cbad9..fb262274b 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -57,12 +57,18 @@ @import "modules/machines/machines-filters"; @import "modules/machines/required-training-modal"; @import "modules/user/avatar"; -@import "modules/pricing/pricing-list"; @import "modules/pricing/editable-price"; -@import "modules/pricing/configure-group-button"; -@import "modules/pricing/group-form"; -@import "modules/pricing/delete-group"; -@import "modules/pricing/edit-group"; +@import "modules/pricing/machines/machines-pricing"; +@import "modules/pricing/machines/configure-packs-button"; +@import "modules/pricing/machines/pack-form"; +@import "modules/pricing/machines/delete-pack"; +@import "modules/pricing/machines/edit-pack"; +@import "modules/pricing/machines/create-pack"; +@import "modules/pricing/spaces/configure-extended-prices-button"; +@import "modules/pricing/spaces/create-extended-price"; +@import "modules/pricing/spaces/delete-extended-price"; +@import "modules/pricing/spaces/edit-extended-price"; +@import "modules/pricing/spaces/spaces-pricing"; @import "modules/settings/check-list-setting"; @import "modules/prepaid-packs/propose-packs-modal"; @import "modules/prepaid-packs/packs-summary"; diff --git a/app/frontend/src/stylesheets/modules/pricing/configure-group-button.scss b/app/frontend/src/stylesheets/modules/pricing/machines/configure-packs-button.scss similarity index 84% rename from app/frontend/src/stylesheets/modules/pricing/configure-group-button.scss rename to app/frontend/src/stylesheets/modules/pricing/machines/configure-packs-button.scss index dec3bf005..7af32419c 100644 --- a/app/frontend/src/stylesheets/modules/pricing/configure-group-button.scss +++ b/app/frontend/src/stylesheets/modules/pricing/machines/configure-packs-button.scss @@ -1,9 +1,9 @@ -.configure-group { +.configure-packs-button { display: inline-block; margin-left: 6px; position: relative; - &-button { + .packs-button { border: 1px solid #d0cccc; border-radius: 50%; cursor: pointer; @@ -18,13 +18,6 @@ color: white; } } - .popover-title { - .add-pack-button { - position: absolute; - right: 5px; - top: 10px; - } - } .popover-content { ul { @@ -44,7 +37,7 @@ line-height: 24px; } - .group-actions button { + .pack-actions button { font-size: 10px; vertical-align: middle; line-height: 10px; diff --git a/app/frontend/src/stylesheets/modules/pricing/machines/create-pack.scss b/app/frontend/src/stylesheets/modules/pricing/machines/create-pack.scss new file mode 100644 index 000000000..72856dca5 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/machines/create-pack.scss @@ -0,0 +1,7 @@ +.create-pack { + .add-pack-button { + position: absolute; + right: 5px; + top: 10px; + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/delete-group.scss b/app/frontend/src/stylesheets/modules/pricing/machines/delete-pack.scss similarity index 65% rename from app/frontend/src/stylesheets/modules/pricing/delete-group.scss rename to app/frontend/src/stylesheets/modules/pricing/machines/delete-pack.scss index dd1f2d259..37d87274c 100644 --- a/app/frontend/src/stylesheets/modules/pricing/delete-group.scss +++ b/app/frontend/src/stylesheets/modules/pricing/machines/delete-pack.scss @@ -1,7 +1,7 @@ -.delete-group { +.delete-pack { display: inline; - &-button { + .remove-pack-button { background-color: #cb1117; color: white; } diff --git a/app/frontend/src/stylesheets/modules/pricing/edit-group.scss b/app/frontend/src/stylesheets/modules/pricing/machines/edit-pack.scss similarity index 65% rename from app/frontend/src/stylesheets/modules/pricing/edit-group.scss rename to app/frontend/src/stylesheets/modules/pricing/machines/edit-pack.scss index f8c48ff92..1b87b732c 100644 --- a/app/frontend/src/stylesheets/modules/pricing/edit-group.scss +++ b/app/frontend/src/stylesheets/modules/pricing/machines/edit-pack.scss @@ -1,3 +1,3 @@ -.edit-group { +.edit-pack { display: inline-block; } diff --git a/app/frontend/src/stylesheets/modules/pricing/machines/machines-pricing.scss b/app/frontend/src/stylesheets/modules/pricing/machines/machines-pricing.scss new file mode 100644 index 000000000..99c04a00b --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/machines/machines-pricing.scss @@ -0,0 +1,31 @@ +.machines-pricing { + .fab-alert { + margin: 15px 0; + } + table { + overflow-y: scroll; + thead > tr > th:first-child { + width: 20%; + } + + thead > tr > th.group-name { + width: 20%; + text-transform: uppercase; + font-size: 1.4rem; + } + + thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; + padding: 8px; + line-height: 1.5; + } + + tbody > tr > td { + padding: 8px; + line-height: 1.5; + vertical-align: top; + border-top: 1px solid #ddd; + } + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/group-form.scss b/app/frontend/src/stylesheets/modules/pricing/machines/pack-form.scss similarity index 89% rename from app/frontend/src/stylesheets/modules/pricing/group-form.scss rename to app/frontend/src/stylesheets/modules/pricing/machines/pack-form.scss index 3da69b3e4..1d10d59aa 100644 --- a/app/frontend/src/stylesheets/modules/pricing/group-form.scss +++ b/app/frontend/src/stylesheets/modules/pricing/machines/pack-form.scss @@ -1,4 +1,4 @@ -.group-form { +.pack-form { .interval-inputs { display: flex; diff --git a/app/frontend/src/stylesheets/modules/pricing/spaces/configure-extended-prices-button.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/configure-extended-prices-button.scss new file mode 100644 index 000000000..3931c3a79 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/configure-extended-prices-button.scss @@ -0,0 +1,49 @@ +.configure-extended-prices-button { + display: inline-block; + margin-left: 6px; + position: relative; + + .extended-prices-button { + border: 1px solid #d0cccc; + border-radius: 50%; + cursor: pointer; + width: 30px; + height: 30px; + display: inline-block; + padding: 2px 6px; + box-shadow: 0 1px 1px 0 #abaaaa; + + &:hover { + background-color: #b9b9b9; + color: white; + } + } + + .popover-content { + ul { + padding-left: 19px; + + li { + display: flex; + justify-content: space-between; + &::before { + content: '\f466'; + font-family: 'Font Awesome 5 Free'; + position: absolute; + left: 11px; + font-weight: 800; + font-size: 12px; + vertical-align: middle; + line-height: 24px; + } + + .extended-prices-actions button { + font-size: 10px; + vertical-align: middle; + line-height: 10px; + height: auto; + } + } + } + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/spaces/create-extended-price.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/create-extended-price.scss new file mode 100644 index 000000000..6f87fab8b --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/create-extended-price.scss @@ -0,0 +1,7 @@ +.create-extended-price { + .add-price-button { + position: absolute; + right: 5px; + top: 10px; + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/spaces/delete-extended-price.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/delete-extended-price.scss new file mode 100644 index 000000000..a19f09c09 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/delete-extended-price.scss @@ -0,0 +1,8 @@ +.delete-extended-price { + display: inline; + + .remove-price-button { + background-color: #cb1117; + color: white; + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/spaces/edit-extended-price.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/edit-extended-price.scss new file mode 100644 index 000000000..98ce6bfde --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/edit-extended-price.scss @@ -0,0 +1,3 @@ +.edit-extended-price { + display: inline-block; +} diff --git a/app/frontend/src/stylesheets/modules/pricing/spaces/extended-price-form.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/extended-price-form.scss new file mode 100644 index 000000000..312ea1f0b --- /dev/null +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/extended-price-form.scss @@ -0,0 +1,10 @@ +.extended-price-form { + .interval-inputs { + display: flex; + + .select-interval { + min-width: 49%; + margin-left: 4px; + } + } +} diff --git a/app/frontend/src/stylesheets/modules/pricing/pricing-list.scss b/app/frontend/src/stylesheets/modules/pricing/spaces/spaces-pricing.scss similarity index 96% rename from app/frontend/src/stylesheets/modules/pricing/pricing-list.scss rename to app/frontend/src/stylesheets/modules/pricing/spaces/spaces-pricing.scss index 5169794f4..b449492de 100644 --- a/app/frontend/src/stylesheets/modules/pricing/pricing-list.scss +++ b/app/frontend/src/stylesheets/modules/pricing/spaces/spaces-pricing.scss @@ -1,4 +1,4 @@ -.pricing-list { +.spaces-pricing { .fab-alert { margin: 15px 0; } @@ -28,4 +28,4 @@ border-top: 1px solid #ddd; } } -} \ No newline at end of file +} diff --git a/app/models/price.rb b/app/models/price.rb index 94e2dc5e9..b96d10406 100644 --- a/app/models/price.rb +++ b/app/models/price.rb @@ -8,4 +8,8 @@ class Price < ApplicationRecord validates :priceable, :group_id, :amount, presence: true validates :priceable_id, uniqueness: { scope: %i[priceable_type plan_id group_id duration] } + + def safe_destroy + destroy unless duration == 60 + end end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index a2cac5534..d041ec00e 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -369,6 +369,11 @@ en: status_disabled: "Disabled" status_all: "All" spaces_pricing: + prices_match_space_hours_rates_html: "The prices below match one hour of space reservation, without subscription." + prices_calculated_on_hourly_rate_html: "All the prices will be automatically calculated based on the hourly rate defined here.
    For example, if you define an hourly rate at {RATE}: a slot of {DURATION} minutes, will be charged {PRICE}." + you_can_override: "You can override this duration for each availability you create in the agenda. The price will then be adjusted accordingly." + extended_prices: "Moreover, you can define extended prices which will apply in priority over the hourly rate below. Extended prices allow you, for example, to set a favorable price for a booking of several hours." + spaces: "Spaces" price_updated: "Price successfully updated" machines_pricing: prices_match_machine_hours_rates_html: "The prices below match one hour of machine usage, without subscription." @@ -380,10 +385,12 @@ en: packs: "Prepaid packs" no_packs: "No packs for now" pack_DURATION: "{DURATION} hours" - configure_extendedPrices_button: - extendedPrices: "Extended prices" - no_extendedPrices: "No extended price for now" - extended_prices_form: + configure_extended_prices_button: + extended_prices: "Extended prices" + no_extended_prices: "No extended price for now" + extended_price_DURATION: "{DURATION} minutes" + extended_price_form: + duration: "Duration (minutes)" amount: "Price" pack_form: hours: "Hours" @@ -411,21 +418,21 @@ en: edit_pack: "Edit the pack" confirm_changes: "Confirm changes" pack_successfully_updated: "The prepaid pack was successfully updated." - create_extendedPrice: - new_extendedPrice: "New extended price" - new_extendedPrice_info: "Extended prices allows you to define prices based on custom durations, intead on the default hourly rates." - create_extendedPrice: "Create extended price" - extendedPrice_successfully_created: "The new extended price was successfully created." - delete_extendedPrice: - extendedPrice_deleted: "The extended price was successfully deleted." + 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." + create_extended_price: "Create extended price" + extended_price_successfully_created: "The new extended price was successfully created." + delete_extended_price: + extended_price_deleted: "The extended price was successfully deleted." unable_to_delete: "Unable to delete the extended price: " - delete_extendedPrice: "Delete the extended price" + delete_extended_price: "Delete the extended price" confirm_delete: "Delete" - delete_confirmation: "Are you sure you want to delete this extended price? This won't be possible if it was already bought by users." - edit_extendedPrice: - edit_extendedPrice: "Edit the extended price" + delete_confirmation: "Are you sure you want to delete this extended price?" + edit_extended_price: + edit_extended_price: "Edit the extended price" confirm_changes: "Confirm changes" - extendedPrice_successfully_updated: "The extended price was successfully updated." + extended_price_successfully_updated: "The extended price was successfully updated." #ajouter un code promotionnel coupons_new: add_a_coupon: "Add a coupon"
    {t('app.admin.pricing.spaces')}{t('app.admin.spaces_pricing.spaces')}{group.name}