diff --git a/src/main/frontend/src/components/AddCatalogPriceForm.js b/src/main/frontend/src/components/AddCatalogPriceForm.js
deleted file mode 100644
index e3a8234d3e..0000000000
--- a/src/main/frontend/src/components/AddCatalogPriceForm.js
+++ /dev/null
@@ -1,190 +0,0 @@
-//
-// IMPORTANT:
-// You must update ResourceUrl.RESOURCES_VERSION each time whenever you're modified this file!
-//
-// @todo #1342 AddCatalogPriceForm: add tests
-// @todo #1388 AddCatalogPriceForm: consider using a tooltip for currency
-
-class AddCatalogPriceForm extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- price: null,
- catalog: 'michel',
- hasServerError: false,
- validationErrors: [],
- isDisabled: false
- };
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleChangePrice = this.handleChangePrice.bind(this);
- this.handleChangeCatalog = this.handleChangeCatalog.bind(this);
- }
-
- handleChangePrice(event) {
- event.preventDefault();
- this.setState({
- price: event.target.value
- });
- }
-
- handleChangeCatalog(event) {
- event.preventDefault();
- this.setState({
- catalog: event.target.value
- });
- }
-
- handleSubmit(event) {
- event.preventDefault();
-
- this.setState({
- isDisabled: true,
- hasServerError: false,
- validationErrors: []
- });
-
- axios.patch(
- this.props.url,
- [
- {
- op: 'add',
- path: `/${this.state.catalog}_price`,
- value: this.state.price
- }
- ],
- {
- headers: {
- [this.props.csrfHeaderName]: this.props.csrfTokenValue,
- 'Cache-Control': 'no-store'
- },
- validateStatus: status => {
- return status == 204 || status == 400;
- }
- }
- )
- .then(response => {
- const data = response.data;
- if (data.hasOwnProperty('fieldErrors')) {
- const fieldErrors = [];
- if (data.fieldErrors.value) {
- fieldErrors.push(...data.fieldErrors.value);
- }
-
- this.setState({
- isDisabled: false,
- validationErrors: fieldErrors
- });
- return;
- }
-
- // no need to reset the state as page will be reloaded
- window.location.reload();
- })
- .catch(error => {
- console.error(error);
- this.setState({ isDisabled: false, hasServerError: true });
- });
- }
- render() {
-
- return (
-
- );
- }
-}
-
-class AddCatalogPriceFormView extends React.PureComponent {
-
- getCurrencyByCatalogName(catalog) {
- switch (catalog) {
- case 'michel':
- case 'yvert':
- return ['\u20AC', 'EUR'];
- case 'scott':
- return ['$', 'USD'];
- case 'gibbons':
- return ['\u00A3', 'GBP'];
- case 'solovyov':
- case 'zagorski':
- return ['\u20BD', 'RUB'];
- }
- }
- render() {
- const {handleSubmit, hasServerError, handleChangeCatalog, handleChangePrice, validationErrors, isDisabled, catalog, l10n} = this.props;
- const hasValidationErrors = validationErrors.length > 0;
- const [currencySymbol, currencyName] = this.getCurrencyByCatalogName(catalog);
- return (
-
- );
- }
-}
-
-window.AddCatalogPriceForm = AddCatalogPriceForm;
diff --git a/src/main/frontend/webpack.config.js b/src/main/frontend/webpack.config.js
index d6ca663877..33c3da36da 100644
--- a/src/main/frontend/webpack.config.js
+++ b/src/main/frontend/webpack.config.js
@@ -8,7 +8,6 @@ module.exports = {
'utils/CatalogUtils': './src/utils/CatalogUtils.js',
'utils/DateUtils': './src/utils/DateUtils.js',
- 'components/AddCatalogPriceForm': './src/components/AddCatalogPriceForm.js',
'components/AddReleaseYearForm': './src/components/AddReleaseYearForm.js',
'components/HideImageForm': './src/components/HideImageForm.js',
'components/SeriesSaleImportForm': './src/components/SeriesSaleImportForm.js',
diff --git a/src/main/java/ru/mystamps/web/feature/series/AddCatalogPriceForm.java b/src/main/java/ru/mystamps/web/feature/series/AddCatalogPriceForm.java
new file mode 100644
index 0000000000..90e434c193
--- /dev/null
+++ b/src/main/java/ru/mystamps/web/feature/series/AddCatalogPriceForm.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2009-2025 Slava Semushin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package ru.mystamps.web.feature.series;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Getter
+@Setter
+public class AddCatalogPriceForm {
+ @NotNull
+ private StampsCatalog catalogName;
+
+ // @todo #1340 Update series: add validation for a price
+ @NotNull
+ private BigDecimal price;
+}
diff --git a/src/main/java/ru/mystamps/web/feature/series/HtmxSeriesController.java b/src/main/java/ru/mystamps/web/feature/series/HtmxSeriesController.java
index 1b6e101069..ef5049d64b 100644
--- a/src/main/java/ru/mystamps/web/feature/series/HtmxSeriesController.java
+++ b/src/main/java/ru/mystamps/web/feature/series/HtmxSeriesController.java
@@ -34,6 +34,7 @@
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
+import java.math.BigDecimal;
@Controller
@RequiredArgsConstructor
@@ -132,5 +133,49 @@ public String addCatalogNumbers(
return null;
}
+
+ @PatchMapping(
+ path = SeriesUrl.INFO_SERIES_PAGE,
+ headers = "HX-Trigger=add-catalog-price-form"
+ )
+ public String addCatalogPrice(
+ @PathVariable("id") Integer seriesId,
+ @Valid AddCatalogPriceForm form,
+ BindingResult result,
+ @AuthenticationPrincipal CustomUserDetails currentUser,
+ Model model,
+ HttpServletResponse response
+ ) throws IOException {
+
+ if (seriesId == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return null;
+ }
+
+ if (!seriesService.isSeriesExist(seriesId)) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return null;
+ }
+
+ if (result.hasErrors()) {
+ response.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
+ model.addAttribute("isHtmx", true);
+ model.addAttribute("seriesId", seriesId);
+ return "series/info :: AddCatalogPriceForm";
+ }
+
+ Integer currentUserId = currentUser.getUserId();
+ seriesService.addCatalogPrice(
+ form.getCatalogName(),
+ seriesId,
+ form.getPrice(),
+ currentUserId
+ );
+
+ // @todo #1671 AddCatalogPriceForm: update a page without full reload
+ response.addHeader("HX-Refresh", "true");
+
+ return null;
+ }
}
diff --git a/src/main/java/ru/mystamps/web/feature/series/RestSeriesController.java b/src/main/java/ru/mystamps/web/feature/series/RestSeriesController.java
index 856eb68f87..623cfc2982 100644
--- a/src/main/java/ru/mystamps/web/feature/series/RestSeriesController.java
+++ b/src/main/java/ru/mystamps/web/feature/series/RestSeriesController.java
@@ -18,7 +18,6 @@
package ru.mystamps.web.feature.series;
import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
@@ -36,7 +35,6 @@
import javax.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.List;
-import java.util.Locale;
@Validated
@RestController
@@ -46,7 +44,6 @@ class RestSeriesController {
private final SeriesService seriesService;
private final SeriesImageService seriesImageService;
- // @todo #1340 Update series: add validation for a price
// @todo #1343 Update series: add validation for a release year
@PatchMapping(SeriesUrl.INFO_SERIES_PAGE)
public ResponseEntity updateSeries(
@@ -77,19 +74,6 @@ public ResponseEntity updateSeries(
case "/release_year":
seriesService.addReleaseYear(seriesId, patch.integerValue(), currentUserId);
break;
- case "/michel_price":
- case "/scott_price":
- case "/yvert_price":
- case "/gibbons_price":
- case "/solovyov_price":
- case "/zagorski_price":
- seriesService.addCatalogPrice(
- extractCatalog(path),
- seriesId,
- patch.bigDecimalValue(),
- currentUserId
- );
- break;
default:
// @todo #785 Update series: properly fail on invalid path
break;
@@ -116,12 +100,5 @@ public ResponseEntity modifySeriesImage(
return ResponseEntity.noContent().build();
}
- private static StampsCatalog extractCatalog(String path) {
- // "/catalog_something" => "catalog" => "CATALOG"
- String catalogName = StringUtils.substringBetween(path, "/", "_")
- .toUpperCase(Locale.ENGLISH);
- return StampsCatalog.valueOf(catalogName);
- }
-
}
diff --git a/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java b/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java
index 12f01abf0e..334627b2f4 100644
--- a/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java
+++ b/src/main/java/ru/mystamps/web/feature/site/ResourceUrl.java
@@ -31,7 +31,7 @@ public final class ResourceUrl {
public static final String STATIC_RESOURCES_URL = "https://stamps.filezz.ru";
// MUST be updated when any of our resources were modified
- public static final String RESOURCES_VERSION = "v0.4.7.0";
+ public static final String RESOURCES_VERSION = "v0.4.7.2";
private static final String CATALOG_UTILS_JS = "/public/js/" + RESOURCES_VERSION + "/utils/CatalogUtils.min.js";
private static final String COLLECTION_INFO_JS = "/public/js/" + RESOURCES_VERSION + "/collection/info.min.js";
@@ -42,7 +42,6 @@ public final class ResourceUrl {
private static final String SERIES_INFO_JS = "/public/js/" + RESOURCES_VERSION + "/series/info.min.js";
private static final String SALE_IMPORT_FORM_JS = "/public/js/" + RESOURCES_VERSION + "/components/SeriesSaleImportForm.min.js";
private static final String SIMILAR_SERIES_FORM_JS = "/public/js/" + RESOURCES_VERSION + "/components/SimilarSeriesForm.min.js";
- private static final String CATALOG_PRICE_FORM_JS = "/public/js/" + RESOURCES_VERSION + "/components/AddCatalogPriceForm.min.js";
private static final String RELEASE_YEAR_FORM_JS = "/public/js/" + RESOURCES_VERSION + "/components/AddReleaseYearForm.min.js";
private static final String HIDE_IMAGE_FORM_JS = "/public/js/" + RESOURCES_VERSION + "/components/HideImageForm.min.js";
private static final String SERIES_SALES_LIST_JS = "/public/js/" + RESOURCES_VERSION + "/components/SeriesSalesList.min.js";
@@ -81,7 +80,6 @@ public static void exposeResourcesToView(Map resources, String h
put(resources, host, "SERIES_INFO_JS", SERIES_INFO_JS);
put(resources, host, "SALE_IMPORT_FORM_JS", SALE_IMPORT_FORM_JS);
put(resources, host, "SIMILAR_SERIES_FORM_JS", SIMILAR_SERIES_FORM_JS);
- put(resources, host, "CATALOG_PRICE_FORM_JS", CATALOG_PRICE_FORM_JS);
put(resources, host, "RELEASE_YEAR_FORM_JS", RELEASE_YEAR_FORM_JS);
put(resources, host, "HIDE_IMAGE_FORM_JS", HIDE_IMAGE_FORM_JS);
put(resources, host, "SERIES_SALES_LIST_JS", SERIES_SALES_LIST_JS);
diff --git a/src/main/java/ru/mystamps/web/support/spring/mvc/PatchRequest.java b/src/main/java/ru/mystamps/web/support/spring/mvc/PatchRequest.java
index e5c82fa702..4032060b46 100644
--- a/src/main/java/ru/mystamps/web/support/spring/mvc/PatchRequest.java
+++ b/src/main/java/ru/mystamps/web/support/spring/mvc/PatchRequest.java
@@ -24,7 +24,6 @@
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
-import java.math.BigDecimal;
// See for details: http://jsonpatch.com
@Getter
@@ -58,8 +57,4 @@ public Integer integerValue() {
return Integer.valueOf(value);
}
- public BigDecimal bigDecimalValue() {
- return BigDecimalConverter.valueOf(value);
- }
-
}
diff --git a/src/main/javascript/series/info.js b/src/main/javascript/series/info.js
index 1acb0ece42..df4c4a5e83 100644
--- a/src/main/javascript/series/info.js
+++ b/src/main/javascript/series/info.js
@@ -7,3 +7,40 @@ function populateTransactionDateWithTodayDate() {
var today = DateUtils.formatDateToDdMmYyyy(new Date());
$('#date').val(today);
}
+
+function getCurrencyByCatalogName(catalog) {
+ switch (catalog) {
+ case 'MICHEL':
+ case 'YVERT':
+ return [ '\u20AC', 'EUR' ];
+ case 'SCOTT':
+ return [ '$', 'USD' ];
+ case 'GIBBONS':
+ return [ '\u00A3', 'GBP' ];
+ case 'SOLOVYOV':
+ case 'ZAGORSKI':
+ return [ '\u20BD', 'RUB' ];
+ }
+}
+
+function initPriceCatalog() {
+ var catalogNameElem = document.getElementById('price-catalog-name');
+ if (catalogNameElem == null) {
+ console.error("Couldn't initialize catalog name selector: element not found");
+ return;
+ }
+ catalogNameElem.addEventListener('change', function changeCatalogCurrency(elem) {
+ var name = this.value;
+ var info = getCurrencyByCatalogName(this.value);
+ var symbolElem = document.getElementById('js-catalog-price-symbol');
+ if (symbolElem == null) {
+ console.error("Couldn't change currency symbol: element not found");
+ }
+ var titleElem = document.getElementById('catalog-price');
+ if (titleElem == null) {
+ console.error("Couldn't change currency title: element not found");
+ }
+ symbolElem.innerText = info[0];
+ titleElem.title = info[1];
+ });
+}
diff --git a/src/main/webapp/WEB-INF/views/series/info.html b/src/main/webapp/WEB-INF/views/series/info.html
index 0afd487130..9dbc3e9969 100644
--- a/src/main/webapp/WEB-INF/views/series/info.html
+++ b/src/main/webapp/WEB-INF/views/series/info.html
@@ -409,7 +409,100 @@ Hidden images
-
+
@@ -1027,7 +1120,7 @@
Add info about selling/buying thi
src="../../../../../../target/classes/js/utils/DateUtils.min.js"
th:src="${DATE_UTILS_JS}">
-
@@ -1074,6 +1167,16 @@ Add info about selling/buying thi
}
}
});
+
+ // https://htmx.org/events/#htmx:load
+ document.body.addEventListener('htmx:load', function htmxLoadEventHandler(event) {
+ // NOTE: htmx:load event fires in the beginning, when a page has been loaded, and when a new element
+ // has been placed after AJAX request. In both cases we need to register custom event listener for the
+ // add catalog price form. Also, we don't need to do anything, if updated element belongs to another form.
+ if (event.detail.elt.querySelector('#price-catalog-name') != null) {
+ initPriceCatalog();
+ }
+ });
/*/-->
-