From 8df60a8712fbbd72d6d51fa7d2e3bdc24e9c0434 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 8 Nov 2022 16:20:34 +0100 Subject: [PATCH] (ui) refactor space form --- app/frontend/src/javascript/api/space.ts | 21 +++ .../components/spaces/space-form.tsx | 111 +++++++++++++++ .../src/javascript/controllers/spaces.js.erb | 55 +++++--- app/frontend/src/javascript/models/space.ts | 9 +- app/frontend/src/stylesheets/application.scss | 1 + .../modules/spaces/space-form.scss | 11 ++ app/frontend/templates/spaces/_form.html | 131 ------------------ app/frontend/templates/spaces/edit.html | 29 +--- app/frontend/templates/spaces/index.html | 4 +- app/frontend/templates/spaces/new.html | 29 +--- app/frontend/templates/spaces/show.html | 8 +- app/views/api/spaces/_space.json.jbuilder | 10 ++ app/views/api/spaces/index.json.jbuilder | 3 +- app/views/api/spaces/show.json.jbuilder | 11 +- config/locales/app.admin.en.yml | 15 +- config/locales/app.shared.en.yml | 45 ------ 16 files changed, 228 insertions(+), 265 deletions(-) create mode 100644 app/frontend/src/javascript/components/spaces/space-form.tsx create mode 100644 app/frontend/src/stylesheets/modules/spaces/space-form.scss delete mode 100644 app/frontend/templates/spaces/_form.html create mode 100644 app/views/api/spaces/_space.json.jbuilder diff --git a/app/frontend/src/javascript/api/space.ts b/app/frontend/src/javascript/api/space.ts index ea2f7b5ff..f99e7b284 100644 --- a/app/frontend/src/javascript/api/space.ts +++ b/app/frontend/src/javascript/api/space.ts @@ -1,6 +1,7 @@ import apiClient from './clients/api-client'; import { AxiosResponse } from 'axios'; import { Space } from '../models/space'; +import ApiLib from '../lib/api'; export default class SpaceAPI { static async index (): Promise> { @@ -12,4 +13,24 @@ export default class SpaceAPI { const res: AxiosResponse = await apiClient.get(`/api/spaces/${id}`); return res?.data; } + + static async create (space: Space): Promise { + const data = ApiLib.serializeAttachments(space, 'space', ['space_files_attributes', 'space_image_attributes']); + const res: AxiosResponse = await apiClient.post('/api/spaces', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + return res?.data; + } + + static async update (space: Space): Promise { + const data = ApiLib.serializeAttachments(space, 'space', ['space_files_attributes', 'space_image_attributes']); + const res: AxiosResponse = await apiClient.put(`/api/spaces/${space.id}`, data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + return res?.data; + } } diff --git a/app/frontend/src/javascript/components/spaces/space-form.tsx b/app/frontend/src/javascript/components/spaces/space-form.tsx new file mode 100644 index 000000000..bc26323c5 --- /dev/null +++ b/app/frontend/src/javascript/components/spaces/space-form.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { SubmitHandler, useForm, useWatch } from 'react-hook-form'; +import SpaceAPI from '../../api/space'; +import { useTranslation } from 'react-i18next'; +import { FormInput } from '../form/form-input'; +import { FormImageUpload } from '../form/form-image-upload'; +import { IApplication } from '../../models/application'; +import { Loader } from '../base/loader'; +import { react2angular } from 'react2angular'; +import { ErrorBoundary } from '../base/error-boundary'; +import { FormRichText } from '../form/form-rich-text'; +import { FormSwitch } from '../form/form-switch'; +import { FormMultiFileUpload } from '../form/form-multi-file-upload'; +import { FabButton } from '../base/fab-button'; +import { Space } from '../../models/space'; + +declare const Application: IApplication; + +interface SpaceFormProps { + action: 'create' | 'update', + space?: Space, + onError: (message: string) => void, + onSuccess: (message: string) => void, +} + +/** + * Form to edit or create spaces + */ +export const SpaceForm: React.FC = ({ action, space, onError, onSuccess }) => { + const { handleSubmit, register, control, setValue, formState } = useForm({ defaultValues: { ...space } }); + const output = useWatch({ control }); + const { t } = useTranslation('admin'); + + /** + * Callback triggered when the user validates the machine form: handle create or update + */ + const onSubmit: SubmitHandler = (data: Space) => { + SpaceAPI[action](data).then((res) => { + onSuccess(t(`app.admin.space_form.${action}_success`)); + window.location.href = `/#!/spaces/${res.slug}`; + }).catch(error => { + onError(error); + }); + }; + + return ( +
+ + + + + + +
+

{t('app.admin.space_form.attached_files_pdf')}

+
+ + + + + {t('app.admin.space_form.ACTION_space', { ACTION: action })} + + + ); +}; + +const SpaceFormWrapper: React.FC = (props) => { + return ( + + + + + + ); +}; + +Application.Components.component('spaceForm', react2angular(SpaceFormWrapper, ['action', 'space', 'onError', 'onSuccess'])); diff --git a/app/frontend/src/javascript/controllers/spaces.js.erb b/app/frontend/src/javascript/controllers/spaces.js.erb index f46d2755a..2fc4de648 100644 --- a/app/frontend/src/javascript/controllers/spaces.js.erb +++ b/app/frontend/src/javascript/controllers/spaces.js.erb @@ -214,18 +214,22 @@ Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spa /** * Controller used in the space creation page (admin) */ -Application.Controllers.controller('NewSpaceController', ['$scope', '$state', 'CSRF', function ($scope, $state, CSRF) { +Application.Controllers.controller('NewSpaceController', ['$scope', '$state', 'CSRF', 'growl', function ($scope, $state, CSRF, growl) { CSRF.setMetaTags(); - // API URL where the form will be posted - $scope.actionUrl = '/api/spaces/'; + /** + * Callback triggered by react components + */ + $scope.onSuccess = function (message) { + growl.success(message); + }; - // Form action on the above URL - $scope.method = 'post'; - - // default space parameters - $scope.space = - { space_files_attributes: [] }; + /** + * Callback triggered by react components + */ + $scope.onError = function (message) { + growl.error(message); + }; // Using the SpacesController return new SpacesController($scope, $state); @@ -234,18 +238,33 @@ Application.Controllers.controller('NewSpaceController', ['$scope', '$state', 'C /** * Controller used in the space edition page (admin) */ -Application.Controllers.controller('EditSpaceController', ['$scope', '$state', '$transition$', 'spacePromise', 'CSRF', - function ($scope, $state, $transition$, spacePromise, CSRF) { +Application.Controllers.controller('EditSpaceController', ['$scope', '$state', '$transition$', 'spacePromise', 'CSRF', 'growl', + function ($scope, $state, $transition$, spacePromise, CSRF, growl) { CSRF.setMetaTags(); - // API URL where the form will be posted - $scope.actionUrl = `/api/spaces/${$transition$.params().id}`; - - // Form action on the above URL - $scope.method = 'put'; - // space to modify - $scope.space = spacePromise; + $scope.space = cleanSpace(spacePromise); + + /** + * Callback triggered by react components + */ + $scope.onSuccess = function (message) { + growl.success(message); + }; + + /** + * Callback triggered by react components + */ + $scope.onError = function (message) { + growl.error(message); + }; + + // prepare the space for the react-hook-form + function cleanSpace (space) { + delete space.$promise; + delete space.$resolved; + return space; + } // Using the SpacesController return new SpacesController($scope, $state); diff --git a/app/frontend/src/javascript/models/space.ts b/app/frontend/src/javascript/models/space.ts index a5337c437..0a73963ba 100644 --- a/app/frontend/src/javascript/models/space.ts +++ b/app/frontend/src/javascript/models/space.ts @@ -1,3 +1,4 @@ +import { FileType } from './file'; export interface Space { id: number, @@ -6,10 +7,6 @@ export interface Space { slug: string, default_places: number, disabled: boolean, - space_image: string, - space_file_attributes?: { - id: number, - attachment: string, - attachement_url: string, - } + space_image_attributes: FileType, + space_file_attributes?: Array } diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index 4d18c777b..a472696f3 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -98,6 +98,7 @@ @import "modules/settings/check-list-setting"; @import "modules/settings/user-validation-setting"; @import "modules/socials/fab-socials"; +@import "modules/spaces/space-form"; @import "modules/store/_utilities"; @import "modules/store/order-actions.scss"; @import "modules/store/order-item"; diff --git a/app/frontend/src/stylesheets/modules/spaces/space-form.scss b/app/frontend/src/stylesheets/modules/spaces/space-form.scss new file mode 100644 index 000000000..8edb03a3b --- /dev/null +++ b/app/frontend/src/stylesheets/modules/spaces/space-form.scss @@ -0,0 +1,11 @@ +.space-form { + .space-files-header.form-item-header p { + cursor: default; + } + .space-files { + margin-bottom: 1.4rem; + } + .submit-btn { + float: right; + } +} diff --git a/app/frontend/templates/spaces/_form.html b/app/frontend/templates/spaces/_form.html deleted file mode 100644 index 907a00cd7..000000000 --- a/app/frontend/templates/spaces/_form.html +++ /dev/null @@ -1,131 +0,0 @@ -{{alert.msg}} - -
- -
- - {{ 'app.shared.space.name_is_required' }} -
-
- -
- -
-
-
- -
-
- -
-
- - {{ 'app.shared.space.add_an_illustration' | translate }} - {{ 'app.shared.buttons.change' }} - - - {{ 'app.shared.buttons.delete' }} -
-
-
-
- - -
- -
- - {{ 'app.shared.space.default_places_is_required' }} -
-
- - -
- -
- - - -
-
- -
- -
- - - -
-
- -
- -
-
- - - -
-
- {{file.attachment}} -
- - {{ 'app.shared.space.attach_a_file' }} - {{ 'app.shared.buttons.change' }} - - - -
- -
- {{ 'app.shared.space.add_an_attachment' | translate }} -
-
- - -
- -
- - -
-
diff --git a/app/frontend/templates/spaces/edit.html b/app/frontend/templates/spaces/edit.html index 3a0d6ee7a..a1e0d3a2f 100644 --- a/app/frontend/templates/spaces/edit.html +++ b/app/frontend/templates/spaces/edit.html @@ -20,30 +20,11 @@
-
- - - -
-
- -
- - -
-
+
+
+ +
+
diff --git a/app/frontend/templates/spaces/index.html b/app/frontend/templates/spaces/index.html index 7d56ebd4b..3dd5ce601 100644 --- a/app/frontend/templates/spaces/index.html +++ b/app/frontend/templates/spaces/index.html @@ -48,10 +48,10 @@
-
+
-
+

{{space.name}}

diff --git a/app/frontend/templates/spaces/new.html b/app/frontend/templates/spaces/new.html index 355b03741..31e9fd33e 100644 --- a/app/frontend/templates/spaces/new.html +++ b/app/frontend/templates/spaces/new.html @@ -26,30 +26,11 @@ {{ 'app.admin.space_new.consider_changing_its_prices_before_creating_any_reservation_slot' | translate }}
-
- - - -
-
- -
- - -
-
+
+
+ +
+
diff --git a/app/frontend/templates/spaces/show.html b/app/frontend/templates/spaces/show.html index a2899e171..ed36a57aa 100644 --- a/app/frontend/templates/spaces/show.html +++ b/app/frontend/templates/spaces/show.html @@ -30,8 +30,8 @@
-
- {{space.name}} +
+ {{space.name}}

@@ -42,7 +42,7 @@
-
+

{{ 'app.public.space_show.characteristics' }}

@@ -60,7 +60,7 @@ diff --git a/app/views/api/spaces/_space.json.jbuilder b/app/views/api/spaces/_space.json.jbuilder new file mode 100644 index 000000000..632a0fd60 --- /dev/null +++ b/app/views/api/spaces/_space.json.jbuilder @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +json.extract! space, :id, :name, :description, :slug, :default_places, :disabled +if space.space_image + json.space_image_attributes do + json.id space.space_image.id + json.attachment_name space.space_image.attachment_identifier + json.attachment_url space.space_image.attachment.url + end +end diff --git a/app/views/api/spaces/index.json.jbuilder b/app/views/api/spaces/index.json.jbuilder index 97572fe74..cb524287f 100644 --- a/app/views/api/spaces/index.json.jbuilder +++ b/app/views/api/spaces/index.json.jbuilder @@ -1,6 +1,5 @@ # frozen_string_literal: true json.array!(@spaces) do |space| - json.extract! space, :id, :name, :description, :slug, :default_places, :disabled - json.space_image space.space_image.attachment.medium.url if space.space_image + json.partial! 'api/spaces/space', space: space end diff --git a/app/views/api/spaces/show.json.jbuilder b/app/views/api/spaces/show.json.jbuilder index 15147739f..7127304e0 100644 --- a/app/views/api/spaces/show.json.jbuilder +++ b/app/views/api/spaces/show.json.jbuilder @@ -1,14 +1,9 @@ # frozen_string_literal: true -json.extract! @space, :id, :name, :description, :characteristics, :created_at, :updated_at, :slug, :default_places, :disabled -json.space_image @space.space_image.attachment.large.url if @space.space_image +json.partial! 'api/spaces/space', space: @space +json.extract! @space, :characteristics, :created_at, :updated_at json.space_files_attributes @space.space_files do |f| json.id f.id - json.attachment f.attachment_identifier + json.attachment_name f.attachment_identifier json.attachment_url f.attachment_url end -# Unused for the moment. May be used to show a list of projects -# using the space in the space_show screen -# json.space_projects @space.projects do |p| -# json.extract! p, :slug, :name -# end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index c273b0c0f..adbef06e3 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -8,7 +8,6 @@ en: description: "Description" technical_specifications: "Technical specifications" attached_files_pdf: "Attached files (pdf)" - attach_a_file: "Attach a file" add_an_attachment: "Add an attachment" disable_machine: "Disable machine" disabled_help: "When disabled, the machine won't be reservable and won't appear by default in the machines list." @@ -31,6 +30,20 @@ en: ACTION_training: "{ACTION, select, create{Create} other{Update}} the training" create_success: "The training was created successfully" update_success: "The training was updated successfully" + space_form: + name: "Name" + illustration: "Illustration" + add_an_illustration: "Add an illustration" + description: "Description" + characteristics: "Characteristics" + attached_files_pdf: "Attached files (pdf)" + add_an_attachment: "Add an attachment" + default_seats: "Default number of seats" + disable_space: "Disable the space" + disabled_help: "When disabled, the space won't be reservable and won't appear by default in the spaces list." + ACTION_space: "{ACTION, select, create{Create} other{Update}} the space" + create_success: "The space was created successfully" + update_success: "The space was updated successfully" #add a new machine machines_new: declare_a_new_machine: "Declare a new machine" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 8dbc6c09b..b206912de 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -141,21 +141,6 @@ en: themes: "Themes" tags: "Tags" save_as_draft: "Save as draft" - #machine edition form - machine: - name: "Name" - name_is_required: "Name is required." - illustration: "Visual" - add_an_illustration: "Add a visual" - description: "Description" - description_is_required: "Description is required." - technical_specifications: "Technical specifications" - technical_specifications_are_required: "Technical specifications are required." - attached_files_pdf: "Attached files (pdf)" - attach_a_file: "Attach a file" - add_an_attachment: "Add an attachment" - disable_machine: "Disable machine" - validate_your_machine: "Validate your machine" #button to book a machine reservation reserve_button: book_this_machine: "Book this machine" @@ -267,20 +252,6 @@ en: email_address_is_required: "Email address is required." disabled: "Disable subscription" disable_plan_will_not_unsubscribe_users: "Beware: disabling this plan won't unsubscribe users having active subscriptions with it." - #training edition form - trainings: - name: "Name" - name_is_required: "Name is required." - illustration: "Illustration" - add_an_illustration: "Add an illustration" - description: "Description" - description_is_required: "Description is required." - add_a_new_training: "Add a new training" - validate_your_training: "Validate your training" - associated_machines: "Associated machines" - number_of_tickets: "Number of tickets" - public_page: "Show in training lists" - disable_training: "Disable the training" #partial form to edit/create a user (admin view) user_admin: user: "User" @@ -397,22 +368,6 @@ en: unable_to_apply_the_coupon_because_amount_exceeded: "Unable to apply the coupon: the discount exceed the total amount of this purchase." unable_to_apply_the_coupon_because_undefined: "Unable to apply the coupon: an unexpected error occurred, please contact the Fablab's manager." unable_to_apply_the_coupon_because_rejected: "This code does not exists." - #form to create/edit a space - space: - name: "Name" - name_is_required: "Name is required." - illustration: "Illustration" - add_an_illustration: "Add an illustration" - description: "Description" - description_is_required: "Description is required." - characteristics: "Characteristics" - characteristics_are_required: "Characteristics are required." - attached_files_pdf: "Attached files (pdf)" - attach_a_file: "Attach a file" - add_an_attachment: "Add an attachment" - default_places: "Default maximum tickets" - default_places_is_required: "Default maximum tickets is required." - disable_space: "Disable space" payment_schedule_summary: your_payment_schedule: "Your payment schedule" NUMBER_monthly_payment_of_AMOUNT: "{NUMBER} monthly {NUMBER, plural, =1{payment} other{payments}} of {AMOUNT}"