feat(tariffs): configurable purchase order for periods and packages

The order of enabled_periods (period tariffs) and traffic_packages
(traffic tariffs) is now the storefront order everywhere — both the
Telegram keyboard and the web app. Only new tariffs-config tariffs are
affected; legacy subscription/traffic options are untouched.

- Stop sorting periods and traffic packages in the web app plans
  serializer so it follows the configured order, matching the bot
  keyboards that already iterate the lists as-is.
- Preserve the row order through the admin draft (load and save) instead
  of sorting by months.
- Add a reusable Sortable component to the UI library (native HTML5
  drag & drop with a grip handle; bits-ui/shadcn have no such primitive)
  and use it to reorder period rows and traffic package rows in the
  tariff editor.
This commit is contained in:
3252a8
2026-06-03 10:14:40 +03:00
parent 0a294b8bf8
commit 58de153370
13 changed files with 396 additions and 129 deletions
+3 -3
View File
@@ -69,6 +69,8 @@ export function rowsFromPackages(packageSet, currency, valueKey) {
export function draftFromTariff(tariff, defaultCurrency = "rub") {
const currency = normalizeCurrencyKey(defaultCurrency);
const defaultPrices = tariff.prices?.[currency] || {};
// enabled_periods comes first so its order (the configured purchase order)
// is preserved; any extra price-only months are appended afterwards.
const months = new Set([
...(tariff.enabled_periods || []),
...Object.keys(defaultPrices).map(Number),
@@ -77,7 +79,6 @@ export function draftFromTariff(tariff, defaultCurrency = "rub") {
]);
const periodRows = [...months]
.filter((month) => Number.isFinite(month) && month > 0)
.sort((a, b) => a - b)
.map((month) => ({
months: month,
rub:
@@ -231,8 +232,7 @@ export function tariffFromDraft(draft, fallbackCurrency = "rub") {
if (seenMonths.has(row.months)) return false;
seenMonths.add(row.months);
return true;
})
.sort((a, b) => a.months - b.months);
});
tariff.monthly_gb = parseNumber(draft.monthly_gb, 0);
tariff.enabled_periods = rows.map((row) => row.months);
const defaultPrices = Object.fromEntries(rows.map((row) => [String(row.months), row.rub || 0]));