@webx-ui/module-tariffs 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +73 -73
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { reorderItems as me, useAdmin as ee, useTranslate as oe, useErrorText as ne,
|
|
2
|
-
import { defineComponent as le, ref as S, computed as v, onMounted as
|
|
3
|
-
import { onBeforeRouteUpdate as
|
|
4
|
-
import { useLocales as
|
|
1
|
+
import { reorderItems as me, useAdmin as ee, useTranslate as oe, useErrorText as ne, useBodyKeys as ye, WxScreenHead as be, WxScreen as we, WxSaveState as xe, useItemOrder as _e, WxListScreen as ke, WxRowMenu as Te, categoryRoutes as Se } from "@webx-ui/module-admin";
|
|
2
|
+
import { defineComponent as le, ref as S, computed as v, onMounted as We, onBeforeUnmount as fe, openBlock as p, createElementBlock as A, createBlock as k, unref as a, Fragment as Q, createVNode as b, createSlots as qe, withCtx as g, createTextVNode as O, toDisplayString as C, createCommentVNode as B, watch as re, createElementVNode as I, normalizeClass as H, resolveDynamicComponent as Ne, renderList as $e } from "vue";
|
|
3
|
+
import { onBeforeRouteUpdate as Be, onBeforeRouteLeave as Ce, useRoute as Le, useRouter as Ae } from "vue-router";
|
|
4
|
+
import { useLocales as Pe, localizedValue as Re, WxSkeleton as ue, WxBadge as X, WxActionBar as Ve, WxButton as De, toast as x, confirm as Y, WxCard as Ie, WxListDetail as Ue, WxEmpty as ie, WxInput as ze, WxIcon as j, WxSelect as Oe, WxSortableList as Me, WxAlert as Ee } from "@webx-ui/core";
|
|
5
5
|
const Z = "tariffs";
|
|
6
6
|
function ce(s) {
|
|
7
7
|
const l = `${s.apiPath}/${Z}`, n = (t) => t.data;
|
|
@@ -20,7 +20,7 @@ function ce(s) {
|
|
|
20
20
|
reorder: (t, u = null) => me(s, Z, t, u)
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
const
|
|
23
|
+
const Fe = {
|
|
24
24
|
module: {
|
|
25
25
|
group: "Tariffs",
|
|
26
26
|
tariffs: "Tariffs",
|
|
@@ -124,11 +124,11 @@ const Ee = {
|
|
|
124
124
|
function de() {
|
|
125
125
|
try {
|
|
126
126
|
const { i18n: s } = ee();
|
|
127
|
-
se.has(s) || (s.defaults("webx-tariffs",
|
|
127
|
+
se.has(s) || (s.defaults("webx-tariffs", Fe), se.add(s));
|
|
128
128
|
} catch {
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
function
|
|
131
|
+
function Ge(s, l) {
|
|
132
132
|
if (s.price === null || s.price === void 0) return (s.price_text ?? "").trim();
|
|
133
133
|
const n = Number.isInteger(s.price), t = new Intl.NumberFormat(l, {
|
|
134
134
|
minimumFractionDigits: n ? 0 : 2,
|
|
@@ -136,7 +136,7 @@ function Fe(s, l) {
|
|
|
136
136
|
}).format(s.price), u = s.symbol ?? s.currency ?? "", r = (s.period ?? "").trim();
|
|
137
137
|
return [`${u}${t}`, r].filter((q) => q !== "").join(" ");
|
|
138
138
|
}
|
|
139
|
-
const
|
|
139
|
+
const He = /* @__PURE__ */ le({
|
|
140
140
|
name: "WxTariffPane",
|
|
141
141
|
__name: "TariffPane",
|
|
142
142
|
props: {
|
|
@@ -146,11 +146,11 @@ const Ge = /* @__PURE__ */ le({
|
|
|
146
146
|
},
|
|
147
147
|
emits: ["back", "saved", "created", "removed"],
|
|
148
148
|
setup(s, { emit: l }) {
|
|
149
|
-
const n = s, t = l, u = ee(), r = ce(u), q =
|
|
149
|
+
const n = s, t = l, u = ee(), r = ce(u), q = Pe();
|
|
150
150
|
de();
|
|
151
|
-
const f = oe("webx-tariffs"), P = ne(), _ = S(null), m = S({}), L = S(""), N = S(n.id !== null), c = S(!1), W = S({}), w = v(() => u.can("tariffs.manage")), R = v(() => JSON.stringify(m.value)), y = v(() => L.value !== "" && R.value !== L.value), M = v(() => c.value ? "saving" : y.value || n.id === null ? "unsaved" : "saved"),
|
|
151
|
+
const f = oe("webx-tariffs"), P = ne(), _ = S(null), m = S({}), L = S(""), N = S(n.id !== null), c = S(!1), W = S({}), w = v(() => u.can("tariffs.manage")), R = v(() => JSON.stringify(m.value)), y = v(() => L.value !== "" && R.value !== L.value), M = v(() => c.value ? "saving" : y.value || n.id === null ? "unsaved" : "saved"), K = v(() => {
|
|
152
152
|
const i = m.value.name, d = n.id === null ? f("tariff.new") : _.value?.name ?? "";
|
|
153
|
-
return
|
|
153
|
+
return Re(i, q.active.value, d) || f("tariff.untitled");
|
|
154
154
|
});
|
|
155
155
|
function $(i) {
|
|
156
156
|
_.value = i.tariff, m.value = i.values, L.value = JSON.stringify(i.values);
|
|
@@ -198,7 +198,7 @@ const Ge = /* @__PURE__ */ le({
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
async function
|
|
201
|
+
async function J() {
|
|
202
202
|
const i = _.value;
|
|
203
203
|
if (!(!i || !await Y({
|
|
204
204
|
title: f("tariff.delete-title", { name: i.name }),
|
|
@@ -209,17 +209,17 @@ const Ge = /* @__PURE__ */ le({
|
|
|
209
209
|
})))
|
|
210
210
|
try {
|
|
211
211
|
await r.remove(i.id), x.success(f("tariff.deleted")), L.value = R.value, t("removed", i.id);
|
|
212
|
-
} catch (
|
|
213
|
-
x.danger(P(
|
|
212
|
+
} catch (D) {
|
|
213
|
+
x.danger(P(D));
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
function T(i) {
|
|
217
217
|
y.value && i.preventDefault();
|
|
218
218
|
}
|
|
219
|
-
function
|
|
219
|
+
function V(i) {
|
|
220
220
|
(i.ctrlKey || i.metaKey) && i.key.toLowerCase() === "s" && (i.preventDefault(), (y.value || n.id === null) && F());
|
|
221
221
|
}
|
|
222
|
-
|
|
222
|
+
ye(V), We(() => {
|
|
223
223
|
E(), window.addEventListener("beforeunload", T);
|
|
224
224
|
}), fe(() => {
|
|
225
225
|
window.removeEventListener("beforeunload", T);
|
|
@@ -233,9 +233,9 @@ const Ge = /* @__PURE__ */ le({
|
|
|
233
233
|
tone: "danger"
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
-
|
|
236
|
+
Be(
|
|
237
237
|
async (i, d) => i.query.tariff === d.query.tariff ? !0 : await G()
|
|
238
|
-
),
|
|
238
|
+
), Ce(G);
|
|
239
239
|
const U = v(
|
|
240
240
|
() => w.value && _.value !== null ? [
|
|
241
241
|
{
|
|
@@ -245,14 +245,14 @@ const Ge = /* @__PURE__ */ le({
|
|
|
245
245
|
danger: !0,
|
|
246
246
|
menu: !0,
|
|
247
247
|
run: () => {
|
|
248
|
-
|
|
248
|
+
J();
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
] : []
|
|
252
252
|
);
|
|
253
253
|
return (i, d) => (p(), A("div", {
|
|
254
254
|
class: "wx-tariff",
|
|
255
|
-
onKeydown:
|
|
255
|
+
onKeydown: V
|
|
256
256
|
}, [
|
|
257
257
|
N.value ? (p(), k(a(ue), {
|
|
258
258
|
key: 0,
|
|
@@ -260,15 +260,15 @@ const Ge = /* @__PURE__ */ le({
|
|
|
260
260
|
title: "",
|
|
261
261
|
rows: 6
|
|
262
262
|
})) : (p(), A(Q, { key: 1 }, [
|
|
263
|
-
b(a(
|
|
263
|
+
b(a(be), {
|
|
264
264
|
level: 3,
|
|
265
265
|
back: !n.inline,
|
|
266
266
|
"back-label": a(f)("tariff.back"),
|
|
267
|
-
title:
|
|
267
|
+
title: K.value,
|
|
268
268
|
actions: U.value,
|
|
269
269
|
"collapse-below": 480,
|
|
270
|
-
onBack: d[0] || (d[0] = (
|
|
271
|
-
},
|
|
270
|
+
onBack: d[0] || (d[0] = (D) => t("back"))
|
|
271
|
+
}, qe({ _: 2 }, [
|
|
272
272
|
_.value && !_.value.published ? {
|
|
273
273
|
name: "title-after",
|
|
274
274
|
fn: g(() => [
|
|
@@ -282,19 +282,19 @@ const Ge = /* @__PURE__ */ le({
|
|
|
282
282
|
key: "0"
|
|
283
283
|
} : void 0
|
|
284
284
|
]), 1032, ["back", "back-label", "title", "actions"]),
|
|
285
|
-
b(a(
|
|
285
|
+
b(a(we), {
|
|
286
286
|
modelValue: m.value,
|
|
287
|
-
"onUpdate:modelValue": d[1] || (d[1] = (
|
|
287
|
+
"onUpdate:modelValue": d[1] || (d[1] = (D) => m.value = D),
|
|
288
288
|
name: "tariffs.form",
|
|
289
289
|
errors: W.value,
|
|
290
290
|
disabled: !w.value || c.value
|
|
291
291
|
}, null, 8, ["modelValue", "errors", "disabled"]),
|
|
292
|
-
w.value ? (p(), k(a(
|
|
292
|
+
w.value ? (p(), k(a(Ve), { key: 0 }, {
|
|
293
293
|
state: g(() => [
|
|
294
|
-
b(a(
|
|
294
|
+
b(a(xe), { state: M.value }, null, 8, ["state"])
|
|
295
295
|
]),
|
|
296
296
|
default: g(() => [
|
|
297
|
-
b(a(
|
|
297
|
+
b(a(De), {
|
|
298
298
|
type: "primary",
|
|
299
299
|
loading: c.value,
|
|
300
300
|
disabled: !y.value && n.id !== null,
|
|
@@ -316,16 +316,16 @@ const Ge = /* @__PURE__ */ le({
|
|
|
316
316
|
for (const [t, u] of l)
|
|
317
317
|
n[t] = u;
|
|
318
318
|
return n;
|
|
319
|
-
},
|
|
319
|
+
}, Ke = /* @__PURE__ */ pe(He, [["__scopeId", "data-v-196f1a0d"]]), Je = { class: "wx-tariffs__bar" }, je = { class: "wx-tariff-row__body" }, Qe = { class: "wx-tariff-row__who" }, Xe = { class: "wx-tariff-row__name" }, Ye = ["aria-label"], Ze = { class: "wx-tariff-row__facts" }, et = {
|
|
320
320
|
key: 1,
|
|
321
321
|
class: "wx-tariff-row__badge"
|
|
322
|
-
},
|
|
322
|
+
}, tt = /* @__PURE__ */ le({
|
|
323
323
|
__name: "TariffsPage",
|
|
324
324
|
props: {
|
|
325
325
|
base: { default: "/tariffs" }
|
|
326
326
|
},
|
|
327
327
|
setup(s) {
|
|
328
|
-
const l = ee(), n = ce(l), t =
|
|
328
|
+
const l = ee(), n = ce(l), t = Le(), u = Ae();
|
|
329
329
|
de();
|
|
330
330
|
const r = oe("webx-tariffs"), q = ne(), f = S([]), P = S([]), _ = S(!0), m = v(() => l.can("tariffs.manage")), L = v(
|
|
331
331
|
() => l.state.manifest?.modules.find((e) => e.id === "tariffs")?.title ?? r("module.tariffs")
|
|
@@ -349,14 +349,14 @@ const Ge = /* @__PURE__ */ le({
|
|
|
349
349
|
set: (e) => {
|
|
350
350
|
e || i();
|
|
351
351
|
}
|
|
352
|
-
}),
|
|
352
|
+
}), K = v(() => [
|
|
353
353
|
{ value: "", label: r("tariff.all") },
|
|
354
354
|
{ value: "trashed", label: r("tariff.trashed"), icon: "trash" }
|
|
355
|
-
]), $ =
|
|
355
|
+
]), $ = _e(Z, () => ({
|
|
356
356
|
q: w.value,
|
|
357
357
|
category: W.value,
|
|
358
358
|
filtered: c.value
|
|
359
|
-
})), E = v(() => m.value && $.sortable.value), F = v(() => $.mode.value === "category" ? r("tariff.order-group") : $.mode.value === "all" ? r("tariff.order") : $.hint.value),
|
|
359
|
+
})), E = v(() => m.value && $.sortable.value), F = v(() => $.mode.value === "category" ? r("tariff.order-group") : $.mode.value === "all" ? r("tariff.order") : $.hint.value), J = v(() => w.value !== "" ? r("tariff.empty-search") : c.value ? r("tariff.empty-bin") : r("tariff.empty"));
|
|
360
360
|
async function T() {
|
|
361
361
|
_.value = !0;
|
|
362
362
|
try {
|
|
@@ -378,12 +378,12 @@ const Ge = /* @__PURE__ */ le({
|
|
|
378
378
|
T();
|
|
379
379
|
}
|
|
380
380
|
), T();
|
|
381
|
-
let
|
|
381
|
+
let V;
|
|
382
382
|
re(R, (e) => {
|
|
383
|
-
clearTimeout(
|
|
383
|
+
clearTimeout(V), V = setTimeout(() => {
|
|
384
384
|
u.replace({ query: { ...t.query, q: e.trim() === "" ? void 0 : e } });
|
|
385
385
|
}, 300);
|
|
386
|
-
}), fe(() => clearTimeout(
|
|
386
|
+
}), fe(() => clearTimeout(V));
|
|
387
387
|
function G(e) {
|
|
388
388
|
u.replace({
|
|
389
389
|
query: { ...t.query, category: typeof e == "number" ? String(e) : void 0 }
|
|
@@ -399,7 +399,7 @@ const Ge = /* @__PURE__ */ le({
|
|
|
399
399
|
function d(e) {
|
|
400
400
|
U(e.id), T();
|
|
401
401
|
}
|
|
402
|
-
async function
|
|
402
|
+
async function D() {
|
|
403
403
|
try {
|
|
404
404
|
await $.move(f.value.map((e) => e.id));
|
|
405
405
|
} catch (e) {
|
|
@@ -455,22 +455,22 @@ const Ge = /* @__PURE__ */ le({
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
function ae(e) {
|
|
458
|
-
return
|
|
458
|
+
return Ge(e, l.i18n.state.locale);
|
|
459
459
|
}
|
|
460
|
-
return (e, h) => (p(), k(a(
|
|
460
|
+
return (e, h) => (p(), k(a(ke), {
|
|
461
461
|
view: N.value,
|
|
462
462
|
"onUpdate:view": h[4] || (h[4] = (o) => N.value = o),
|
|
463
463
|
title: L.value,
|
|
464
|
-
views:
|
|
464
|
+
views: K.value,
|
|
465
465
|
card: !1
|
|
466
466
|
}, {
|
|
467
467
|
default: g(() => [
|
|
468
|
-
b(a(
|
|
468
|
+
b(a(Ie), {
|
|
469
469
|
class: "wx-tariffs",
|
|
470
470
|
padding: "none"
|
|
471
471
|
}, {
|
|
472
472
|
default: g(() => [
|
|
473
|
-
b(a(
|
|
473
|
+
b(a(Ue), {
|
|
474
474
|
open: M.value,
|
|
475
475
|
"onUpdate:open": h[3] || (h[3] = (o) => M.value = o),
|
|
476
476
|
class: "wx-tariffs__panes",
|
|
@@ -479,8 +479,8 @@ const Ge = /* @__PURE__ */ le({
|
|
|
479
479
|
"detail-label": a(r)("module.tariffs")
|
|
480
480
|
}, {
|
|
481
481
|
list: g(() => [
|
|
482
|
-
|
|
483
|
-
b(a(
|
|
482
|
+
I("div", Je, [
|
|
483
|
+
b(a(ze), {
|
|
484
484
|
modelValue: R.value,
|
|
485
485
|
"onUpdate:modelValue": h[0] || (h[0] = (o) => R.value = o),
|
|
486
486
|
class: "wx-tariffs__search",
|
|
@@ -497,7 +497,7 @@ const Ge = /* @__PURE__ */ le({
|
|
|
497
497
|
]),
|
|
498
498
|
_: 1
|
|
499
499
|
}, 8, ["modelValue", "placeholder", "aria-label"]),
|
|
500
|
-
c.value ? B("", !0) : (p(), k(a(
|
|
500
|
+
c.value ? B("", !0) : (p(), k(a(Oe), {
|
|
501
501
|
key: 0,
|
|
502
502
|
"model-value": W.value,
|
|
503
503
|
options: P.value.map((o) => ({ value: o.id, label: o.title })),
|
|
@@ -526,10 +526,10 @@ const Ge = /* @__PURE__ */ le({
|
|
|
526
526
|
rows: 5
|
|
527
527
|
})) : f.value.length === 0 ? (p(), k(a(ie), {
|
|
528
528
|
key: 2,
|
|
529
|
-
title:
|
|
529
|
+
title: J.value,
|
|
530
530
|
description: !c.value && w.value === "" ? a(r)("tariff.empty-help") : void 0
|
|
531
531
|
}, null, 8, ["title", "description"])) : (p(), A(Q, { key: 3 }, [
|
|
532
|
-
b(a(
|
|
532
|
+
b(a(Me), {
|
|
533
533
|
modelValue: f.value,
|
|
534
534
|
"onUpdate:modelValue": h[2] || (h[2] = (o) => f.value = o),
|
|
535
535
|
class: H(["wx-tariffs__list", { "is-loading": _.value }]),
|
|
@@ -538,18 +538,18 @@ const Ge = /* @__PURE__ */ le({
|
|
|
538
538
|
handle: E.value ? "grip" : "row",
|
|
539
539
|
"item-label": (o) => o.name,
|
|
540
540
|
disabled: !E.value,
|
|
541
|
-
onMove:
|
|
541
|
+
onMove: D
|
|
542
542
|
}, {
|
|
543
543
|
default: g(({ item: o }) => [
|
|
544
|
-
(p(), k(
|
|
544
|
+
(p(), k(Ne(c.value ? "div" : "button"), {
|
|
545
545
|
type: c.value ? void 0 : "button",
|
|
546
546
|
class: H(["wx-tariff-row", { "is-current": y.value === o.id }]),
|
|
547
547
|
onClick: (z) => c.value ? void 0 : U(o.id)
|
|
548
548
|
}, {
|
|
549
549
|
default: g(() => [
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
550
|
+
I("span", je, [
|
|
551
|
+
I("span", Qe, [
|
|
552
|
+
I("span", Xe, C(o.name), 1),
|
|
553
553
|
o.featured ? (p(), A("span", {
|
|
554
554
|
key: 0,
|
|
555
555
|
class: "wx-tariff-row__star",
|
|
@@ -560,12 +560,12 @@ const Ge = /* @__PURE__ */ le({
|
|
|
560
560
|
name: "star",
|
|
561
561
|
size: "sm"
|
|
562
562
|
})
|
|
563
|
-
], 8,
|
|
563
|
+
], 8, Ye)) : B("", !0)
|
|
564
564
|
]),
|
|
565
|
-
|
|
565
|
+
I("span", {
|
|
566
566
|
class: H(["wx-tariff-row__price", { "is-missing": ae(o) === "" }])
|
|
567
567
|
}, C(ae(o) || a(r)("tariff.no-price")), 3),
|
|
568
|
-
|
|
568
|
+
I("span", Ze, [
|
|
569
569
|
o.published ? B("", !0) : (p(), k(a(X), {
|
|
570
570
|
key: 0,
|
|
571
571
|
dot: "",
|
|
@@ -576,8 +576,8 @@ const Ge = /* @__PURE__ */ le({
|
|
|
576
576
|
]),
|
|
577
577
|
_: 1
|
|
578
578
|
})),
|
|
579
|
-
o.badge ? (p(), A("span",
|
|
580
|
-
(p(!0), A(Q, null,
|
|
579
|
+
o.badge ? (p(), A("span", et, C(o.badge), 1)) : B("", !0),
|
|
580
|
+
(p(!0), A(Q, null, $e(o.categories, (z) => (p(), k(a(X), {
|
|
581
581
|
key: z.id,
|
|
582
582
|
size: "sm",
|
|
583
583
|
round: ""
|
|
@@ -594,14 +594,14 @@ const Ge = /* @__PURE__ */ le({
|
|
|
594
594
|
}, 1032, ["type", "class", "onClick"]))
|
|
595
595
|
]),
|
|
596
596
|
actions: g(({ item: o }) => [
|
|
597
|
-
b(a(
|
|
597
|
+
b(a(Te), {
|
|
598
598
|
actions: ve(o),
|
|
599
599
|
label: o.name
|
|
600
600
|
}, null, 8, ["actions", "label"])
|
|
601
601
|
]),
|
|
602
602
|
_: 1
|
|
603
603
|
}, 8, ["modelValue", "class", "handle", "item-label", "disabled"]),
|
|
604
|
-
m.value && !c.value ? (p(), k(a(
|
|
604
|
+
m.value && !c.value ? (p(), k(a(Ee), {
|
|
605
605
|
key: 0,
|
|
606
606
|
class: "wx-tariffs__note",
|
|
607
607
|
type: "info",
|
|
@@ -610,7 +610,7 @@ const Ge = /* @__PURE__ */ le({
|
|
|
610
610
|
], 64))
|
|
611
611
|
]),
|
|
612
612
|
detail: g(({ inline: o, back: z }) => [
|
|
613
|
-
y.value !== null ? (p(), k(
|
|
613
|
+
y.value !== null ? (p(), k(Ke, {
|
|
614
614
|
id: y.value === "new" ? null : y.value,
|
|
615
615
|
key: String(y.value),
|
|
616
616
|
category: W.value,
|
|
@@ -636,8 +636,8 @@ const Ge = /* @__PURE__ */ le({
|
|
|
636
636
|
_: 1
|
|
637
637
|
}, 8, ["view", "title", "views"]));
|
|
638
638
|
}
|
|
639
|
-
}),
|
|
640
|
-
function
|
|
639
|
+
}), at = /* @__PURE__ */ pe(tt, [["__scopeId", "data-v-d86b91c1"]]);
|
|
640
|
+
function rt(s = "/tariffs") {
|
|
641
641
|
return {
|
|
642
642
|
api: "tariffs/categories",
|
|
643
643
|
path: `${s}/groups`,
|
|
@@ -664,7 +664,7 @@ function at(s = "/tariffs") {
|
|
|
664
664
|
}
|
|
665
665
|
};
|
|
666
666
|
}
|
|
667
|
-
function
|
|
667
|
+
function lt(s = {}) {
|
|
668
668
|
const l = s.path ?? "/tariffs";
|
|
669
669
|
return [
|
|
670
670
|
{
|
|
@@ -672,23 +672,23 @@ function nt(s = {}) {
|
|
|
672
672
|
path: l,
|
|
673
673
|
// One route: the open tariff is in the address (`?tariff=`), beside the list it was opened
|
|
674
674
|
// from, so a link to it is a link to both.
|
|
675
|
-
routes: [{ path: l, name: "webx.tariffs", component:
|
|
675
|
+
routes: [{ path: l, name: "webx.tariffs", component: at, props: { base: l } }]
|
|
676
676
|
},
|
|
677
677
|
{
|
|
678
678
|
id: "tariff-groups",
|
|
679
679
|
path: `${l}/groups`,
|
|
680
|
-
routes:
|
|
680
|
+
routes: Se(rt(l))
|
|
681
681
|
}
|
|
682
682
|
];
|
|
683
683
|
}
|
|
684
684
|
export {
|
|
685
685
|
Z as TARIFFS_API,
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
Ke as WxTariffPane,
|
|
687
|
+
at as WxTariffsPage,
|
|
688
688
|
ce as createTariffsApi,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
689
|
+
Ge as priceLine,
|
|
690
|
+
rt as tariffGroupsOptions,
|
|
691
|
+
lt as tariffs,
|
|
692
|
+
Fe as tariffsMessages
|
|
693
693
|
};
|
|
694
694
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/price.ts","../src/TariffPane.vue","../src/TariffsPage.vue","../src/module.ts"],"sourcesContent":["import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { TariffDetail, TariffQuery, TariffRow, TariffsList } from './types'\n\nexport interface TariffsApi {\n /** Every tariff, with the groups the list can be narrowed to. */\n list(query?: TariffQuery): Promise<TariffsList>\n get(id: number): Promise<TariffDetail>\n /**\n * A new tariff out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty card behind.\n */\n create(values: ScreenModel): Promise<TariffDetail>\n /** Refused with a 422 under the name of the field — a line of the list under `features.<n>.text`. */\n save(id: number, values: ScreenModel): Promise<TariffDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<TariffRow>\n /** The order on screen: of the whole list, or — with `category` — of that group alone. */\n reorder(ids: number[], category?: number | null): Promise<void>\n}\n\n/** The path of the list under the panel's API, and of its order (`CategoryRoutes::items()`). */\nexport const TARIFFS_API = 'tariffs'\n\n/**\n * Everything under `/tariffs`, below the panel's API path — except the groups, which are the\n * panel's shared categories and are asked for with `createCategoriesApi(admin, 'tariffs/categories')`.\n */\nexport function createTariffsApi(admin: AdminContext): TariffsApi {\n const base = `${admin.apiPath}/${TARIFFS_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `category=` would ask for the tariffs of no group.\n if (query.search) search.set('search', query.search)\n if (query.category != null) search.set('category', String(query.category))\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<TariffsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: TariffDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: TariffDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: TariffDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's pair: a restored tariff stays closed.\n restore: (id) => admin.http.post<{ data: TariffRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids, category = null) => reorderItems(admin, TARIFFS_API, ids, category),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-tariffs::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal.\n *\n * Categories in the code, groups in the words (decision 14): the `category` group holds what the\n * shared category screens say, and every one of its lines says \"group\".\n */\nexport const tariffsMessages: Record<string, Messages> = {\n module: {\n group: 'Tariffs',\n tariffs: 'Tariffs',\n groups: 'Groups',\n },\n tariff: {\n new: 'New tariff',\n untitled: 'Untitled',\n search: 'Search the tariffs',\n empty: 'No tariffs yet.',\n 'empty-help': 'A tariff reaches the site in a tariffs block, on any page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n trashed: 'Bin',\n 'filter-category': 'Group',\n 'any-category': 'Any group',\n 'not-published': 'Not published',\n featured: 'Recommended',\n 'no-price': 'No price',\n order: 'Drag to change the order of the cards on the site.',\n choose: 'Choose a tariff',\n 'choose-help': 'Or add a new one: the first line of the list.',\n save: 'Save',\n saved: 'The tariff is saved.',\n 'save-failed': 'The tariff was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete the tariff “:name”?',\n 'delete-text':\n 'It goes to the bin and leaves every block on the site. Restored, it comes back to its places.',\n deleted: 'The tariff is in the bin.',\n restore: 'Restore',\n restored: 'The tariff is back.',\n 'reorder-failed': 'The new order was not saved.',\n 'order-group':\n 'Drag to change the order inside this group. The rest of the list keeps its own.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you changed in this tariff is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n },\n screen: {\n tariff: 'Tariff',\n name: 'Name',\n 'name-help':\n 'Required in the default language. Not written in another one, it is shown as written in the default one.',\n badge: 'Badge',\n 'badge-help': 'A short line over the name: “30 HOURS / 25$”.',\n featured: 'Recommended',\n 'featured-help': 'The card stands out among the others.',\n pricing: 'Price',\n price: 'Price',\n 'price-help':\n 'A number, up to two digits after the point. Empty — the words below are shown instead.',\n currency: 'Currency',\n 'currency-help': 'The list comes from the site’s settings.',\n 'currency-placeholder': 'Choose a currency',\n period: 'Period',\n 'period-help': 'Printed after the price: “/mo”, “a year”.',\n 'price-text': 'Price in words',\n 'price-text-help': 'Printed when there is no price: “On request”, “Custom”.',\n features: 'What is included',\n 'features-help': 'A line not written in a language is left out of the list in that language.',\n 'features-add': 'Add a line',\n 'features-empty': 'No lines yet.',\n feature: 'Line',\n about: 'Description',\n description: 'Description',\n 'description-help': 'Shown only in the languages it is written in.',\n button: 'Button',\n 'button-label': 'Label',\n 'button-label-help': 'Without a label in a language, the card has no button in it.',\n 'button-link': 'Link',\n 'button-link-help':\n 'A page of the site or an address. A draft or a page in the bin hides the button.',\n 'button-variant': 'Look',\n 'button-variant-placeholder': 'Choose a look',\n settings: 'Settings',\n categories: 'Groups',\n 'categories-help': 'What a block picks the tariff by, and the tabs of a page of prices.',\n 'categories-add': 'Add to a group',\n 'categories-empty': 'In no group yet.',\n services: 'Services',\n 'services-help':\n 'The services this tariff is for: a tariffs block on a service’s page can show only its own.',\n published: 'Published',\n 'published-help':\n 'On the site in every block that shows its groups, from the moment it is saved.',\n },\n category: {\n 'field-title': 'Title',\n visible: 'Shown on the site',\n 'visible-help':\n 'Hidden, it drops out of every page of prices; its tariffs stay in the blocks that show them.',\n new: 'New group',\n empty: 'No groups yet.',\n 'empty-help':\n 'A group gathers tariffs — for individuals, for business: a block shows the ones you pick, and a page of prices can show each group as a tab.',\n order: 'Drag to change the order of the groups on the site.',\n hidden: 'Hidden from the site',\n tariffs: 'Tariffs: :count',\n 'show-tariffs': 'Show its tariffs',\n 'delete-blocked': 'This group still holds tariffs. Move them first.',\n 'delete-text': 'It goes to the bin and leaves every page of prices. Its tariffs stay.',\n deleted: 'The group is in the bin.',\n saved: 'The group is saved.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { tariffsMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useTariffsMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-tariffs', tariffsMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","import type { TariffRow } from './types'\n\n/**\n * The price of a row on one line, the way the offered block prints it: the symbol before the\n * number, the period after it (“$750 /mo”), or the words where there is no number (“On request”).\n * `''` when there is neither — the row says nothing rather than “0”.\n *\n * The number follows the same rule as the card's `amount` (§4.1): no fraction when it is whole,\n * two places when it is not, the separators of the panel's language. Zero is a number (decision\n * 16): “$0”, and “Free” is the words.\n */\nexport function priceLine(\n row: Pick<TariffRow, 'price' | 'symbol' | 'currency' | 'period' | 'price_text'>,\n locale: string,\n): string {\n if (row.price === null || row.price === undefined) return (row.price_text ?? '').trim()\n\n const whole = Number.isInteger(row.price)\n const amount = new Intl.NumberFormat(locale, {\n minimumFractionDigits: whole ? 0 : 2,\n maximumFractionDigits: whole ? 0 : 2,\n }).format(row.price)\n const symbol = row.symbol ?? row.currency ?? ''\n const period = (row.period ?? '').trim()\n\n return [`${symbol}${amount}`, period].filter((part) => part !== '').join(' ')\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createTariffsApi } from './api'\nimport { useTariffsMessages } from './i18n'\nimport type { TariffDetail, TariffSummary } from './types'\n\n/**\n * One tariff: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `tariffs.form` with a head over it and one button under it. One explicit\n * save, and no autosave: a tariff has no draft (decision 10), so what is saved is on the site at\n * once — the button is the moment somebody decides it is ready.\n *\n * A new tariff is a form first and a record on the first save: an empty row made by pressing\n * \"New tariff\" and walking away would be a tariff with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxTariffPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — a tariff not written yet. */\n id: number | null\n /** The group the list is narrowed to: a new tariff starts in it. */\n category?: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { category: null, inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The tariff was written; the list redraws its row. */\n saved: [tariff: TariffSummary]\n /** The first save of a new tariff: it is a record now, with an id to be opened by. */\n created: [tariff: TariffSummary]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createTariffsApi(context)\nconst locales = useLocales()\nuseTariffsMessages()\n\nconst t = useTranslate('webx-tariffs')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst tariff = ref<TariffSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('tariffs.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.name as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('tariff.new') : (tariff.value?.name ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('tariff.untitled')\n})\n\nfunction take(detail: TariffDetail): void {\n tariff.value = detail.tariff\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for a tariff with nothing in it yet. No currency: the panel does\n // not know the site's list, and the server gives a new tariff its first one (decision 15).\n const blank: ScreenModel = {\n name: {},\n badge: {},\n price: null,\n period: {},\n price_text: {},\n features: [],\n description: {},\n button_label: {},\n button_link: null,\n featured: false,\n published: false,\n categories: props.category === null ? [] : [props.category],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('tariff.saved'))\n if (made) emit('created', detail.tariff)\n else emit('saved', detail.tariff)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('tariff.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = tariff.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('tariff.delete-title', { name: record.name }),\n message: t('tariff.delete-text'),\n confirmText: t('tariff.delete'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('tariff.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', record.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('tariff.leave-title'),\n message: t('tariff.leave-text'),\n confirmText: t('tariff.leave'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another tariff is an update of it rather than a way\n * out — and so is every keystroke in the search above the list, which goes into the address too.\n * Only a change of the tariff asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.tariff === from.query.tariff ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the tariff. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && tariff.value !== null\n ? [\n {\n key: 'delete',\n label: t('tariff.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-tariff\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-tariff__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('tariff.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"tariff && !tariff.published\" #title-after>\n <wx-badge dot>{{ t('tariff.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"tariffs.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('tariff.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-tariff {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-tariff__ghost {\n max-width: 480px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createTariffsApi, TARIFFS_API } from './api'\nimport { useTariffsMessages } from './i18n'\nimport { priceLine } from './price'\nimport TariffPane from './TariffPane.vue'\nimport type { TariffGroupRef, TariffRow, TariffSummary } from './types'\n\n/**\n * The tariffs: all of them on the left, the one being written on the right (§5.2).\n *\n * The shape of the reviews, for the same reason: a price card is short, and the one next to edit\n * is usually the one beside it — \"Growth\" is priced against \"Starter\". On a phone the form is the\n * whole screen, in the drawer `WxListDetail` raises, and draws its own way back.\n *\n * Two orders, as with the reviews: without a filter a drag moves the common order; narrowed to\n * one group, the group's own; a search or the bin shows a selection, so the grips go away and the\n * line under the list says why. The query keeps the shared code's name, `category` (decision 14).\n *\n * Everything the screen is looking at lives in the address — the view, the group, the search and\n * the open tariff — so a link to a tariff is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/tariffs' })\n\nconst context = useAdmin()\nconst api = createTariffsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseTariffsMessages()\n\nconst t = useTranslate('webx-tariffs')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<TariffRow[]>([])\nconst categories = ref<TariffGroupRef[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('tariffs.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'tariffs')?.title ??\n t('module.tariffs'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // A tariff from the list is not a tariff from the bin: the form closes with the view.\n delete query.tariff\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst category = computed<number | null>(() => {\n const raw = route.query.category\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open tariff: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.tariff\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"a tariff is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('tariff.all') },\n { value: 'trashed', label: t('tariff.trashed'), icon: 'trash' },\n])\n\nconst order = useItemOrder(TARIFFS_API, () => ({\n q: q.value,\n category: category.value,\n filtered: inBin.value,\n}))\n\n/* Dragged only by somebody who may write, and only in a list that is a whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\n/*\n * The panel's shared lines say \"category\" and \"items\"; here the editor reads \"group\" and \"cards\"\n * (decision 14). A locked list keeps the shared line: it explains why nothing moves.\n */\nconst hint = computed(() => {\n if (order.mode.value === 'category') return t('tariff.order-group')\n if (order.mode.value === 'all') return t('tariff.order')\n\n return order.hint.value\n})\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('tariff.empty-search')\n\n return inBin.value ? t('tariff.empty-bin') : t('tariff.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n search: q.value,\n category: inBin.value ? null : category.value,\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n categories.value = answer.filters.categories\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, category.value, q.value] as const,\n () => void load(),\n)\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(value: unknown): void {\n void router.replace({\n query: { ...route.query, category: typeof value === 'number' ? String(value) : undefined },\n })\n}\n\n/* `replace` and not `push`: going through twenty tariffs is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, tariff: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.tariff\n void router.replace({ query })\n}\n\n/** A new tariff was saved: it is a record now, and the list has it. */\nfunction created(tariff: TariffSummary): void {\n choose(tariff.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole — the list or the category's, whichever this is. The\n * list is already in its new order (`WxSortableList` reorders the model before it says so), so a\n * failure has to put it back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('tariff.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: TariffRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('tariff.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('tariff.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: TariffRow): Promise<void> {\n const agreed = await confirm({\n title: t('tariff.delete-title', { name: row.name }),\n message: t('tariff.delete-text'),\n confirmText: t('tariff.delete'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('tariff.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: TariffRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('tariff.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* The price as the site would print it — the language of the panel only decides the separators. */\nfunction price(row: TariffRow): string {\n return priceLine(row, context.i18n.state.locale)\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-tariffs\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-tariffs__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.tariffs')\"\n >\n <template #list>\n <div class=\"wx-tariffs__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-tariffs__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('tariff.search')\"\n :aria-label=\"t('tariff.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!-- Always in sight rather than behind a funnel: it is the one filter that decides\n which order a drag writes, and the list says so right under it. -->\n <wx-select\n v-if=\"!inBin\"\n :model-value=\"category\"\n :options=\"categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('tariff.any-category')\"\n :aria-label=\"t('tariff.filter-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow\"\n />\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new tariff is written where\n it will stand, beside the ones it joins — and the form opens the same way any other\n tariff's does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-tariffs__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('tariff.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-tariffs__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('tariff.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a tariff, and a row that both opens\n and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-tariffs__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: TariffRow) => item.name\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-tariff-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <span class=\"wx-tariff-row__body\">\n <span class=\"wx-tariff-row__who\">\n <span class=\"wx-tariff-row__name\">{{ item.name }}</span>\n <!-- A mark and not a control: the switch is in the form. -->\n <span\n v-if=\"item.featured\"\n class=\"wx-tariff-row__star\"\n role=\"img\"\n :aria-label=\"t('tariff.featured')\"\n >\n <wx-icon name=\"star\" size=\"sm\" />\n </span>\n </span>\n\n <span\n class=\"wx-tariff-row__price\"\n :class=\"{ 'is-missing': price(item) === '' }\"\n >\n {{ price(item) || t('tariff.no-price') }}\n </span>\n\n <span class=\"wx-tariff-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('tariff.not-published') }}\n </wx-badge>\n\n <span v-if=\"item.badge\" class=\"wx-tariff-row__badge\">{{ item.badge }}</span>\n\n <wx-badge v-for=\"chip in item.categories\" :key=\"chip.id\" size=\"sm\" round>\n {{ chip.title }}\n </wx-badge>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.name\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-tariffs__note\"\n type=\"info\"\n :description=\"hint\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <tariff-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :category=\"category\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('tariff.choose')\"\n :description=\"canManage ? t('tariff.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-tariffs > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-tariffs__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-tariffs__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n/* The select is a fragment (it carries its dropdown beside it), so our scope attribute never\n reaches it: styled from our own wrapper (CLAUDE.md §4). */\n.wx-tariffs__bar > :deep(.wx-select) {\n flex: 1 1 140px;\n min-width: 0;\n}\n\n.wx-tariffs__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-tariffs__new:hover,\n.wx-tariffs__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-tariffs__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-tariffs__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-tariffs__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-tariffs__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-tariffs__list :deep(.wx-sortable-list__row:hover),\n.wx-tariffs__list :deep(.wx-sortable-list__row:has(.is-current)) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-tariffs__note {\n margin: var(--wx-space-8);\n}\n\n.wx-tariff-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n /* The row is the target: a short name should not leave half the line dead — and a button,\n unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-tariff-row {\n cursor: pointer;\n}\n\n.wx-tariff-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 0;\n}\n\n.wx-tariff-row__who {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-tariff-row__name {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-tariff-row.is-current .wx-tariff-row__name {\n color: var(--wx-color-primary);\n}\n\n.wx-tariff-row__star {\n display: inline-flex;\n flex: none;\n align-self: center;\n color: var(--wx-color-warning);\n}\n\n/* The price is what tells two cards apart in the list, so it is the second line, not a chip. */\n.wx-tariff-row__price {\n overflow: hidden;\n font-size: var(--wx-font-size-sm);\n font-variant-numeric: tabular-nums;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-tariff-row__price.is-missing {\n color: var(--wx-text-muted);\n font-style: italic;\n}\n\n.wx-tariff-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n margin-top: var(--wx-space-2);\n}\n\n.wx-tariff-row__facts:empty {\n display: none;\n}\n\n.wx-tariff-row__badge {\n min-width: 0;\n overflow: hidden;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n text-overflow: ellipsis;\n text-transform: uppercase;\n white-space: nowrap;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport TariffsPage from './TariffsPage.vue'\n\nexport interface TariffsOptions {\n /** Where the tariffs live inside the panel. The groups sit under it. */\n path?: string\n}\n\n/**\n * The groups of the tariffs as the panel's shared category screens see them. Exported so that a\n * panel mounting the screens somewhere of its own does not have to repeat the words.\n *\n * Categories in the code and groups in the words (decision 14): the API and the tables speak the\n * shared code's language, the editor reads \"group\" — “For individuals”, “For business”. No words\n * about addresses: a group has none (decision 2), and the shared list leaves the address line out\n * when the server says so.\n */\nexport function tariffGroupsOptions(path = '/tariffs'): CategoriesOptions {\n return {\n api: 'tariffs/categories',\n path: `${path}/groups`,\n name: 'webx.tariffs.groups',\n module: 'tariff-groups',\n screen: 'tariffs.category-form',\n manage: 'tariffs.groups.manage',\n count: 'tariffs_count',\n // The list narrowed to one group is also where its own order is dragged.\n items: (id) => ({ path, query: { category: String(id) } }),\n words: {\n new: 'webx-tariffs::category.new',\n empty: 'webx-tariffs::category.empty',\n 'empty-help': 'webx-tariffs::category.empty-help',\n order: 'webx-tariffs::category.order',\n hidden: 'webx-tariffs::category.hidden',\n count: 'webx-tariffs::category.tariffs',\n 'show-items': 'webx-tariffs::category.show-tariffs',\n 'delete-blocked': 'webx-tariffs::category.delete-blocked',\n 'delete-text': 'webx-tariffs::category.delete-text',\n deleted: 'webx-tariffs::category.deleted',\n saved: 'webx-tariffs::category.saved',\n 'field-title': 'webx-tariffs::category.field-title',\n },\n }\n}\n\n/**\n * The tariffs as sections of the panel: tariffs, and their groups (§5.2).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * both in the `tariffs` group, which is what draws them under one heading. The panel includes\n * them as `...tariffs()` — the line `extra.webx.panel.register` of the composer package names. A\n * section whose server half is not installed never appears — the entry is built from the manifest.\n */\nexport function tariffs(options: TariffsOptions = {}): AdminModule[] {\n const path = options.path ?? '/tariffs'\n\n return [\n {\n id: 'tariffs',\n path,\n // One route: the open tariff is in the address (`?tariff=`), beside the list it was opened\n // from, so a link to it is a link to both.\n routes: [{ path, name: 'webx.tariffs', component: TariffsPage, props: { base: path } }],\n },\n {\n id: 'tariff-groups',\n path: `${path}/groups`,\n routes: categoryRoutes(tariffGroupsOptions(path)),\n },\n ]\n}\n"],"names":["TARIFFS_API","createTariffsApi","admin","base","data","body","query","search","id","values","ids","category","reorderItems","tariffsMessages","seeded","useTariffsMessages","i18n","useAdmin","priceLine","row","locale","whole","amount","symbol","period","part","props","__props","emit","__emit","context","api","locales","useLocales","t","useTranslate","message","useErrorText","tariff","ref","snapshot","loading","saving","errors","canManage","computed","current","dirty","state","title","written","fallback","localizedValue","take","detail","blank","load","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","_createElementBlock","_createBlock","_unref","WxSkeleton","_Fragment","_createVNode","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","WxButton","route","useRoute","router","useRouter","rows","categories","module","view","value","inBin","raw","q","typed","selected","open","close","views","order","useItemOrder","sortable","hint","emptyText","answer","watch","pause","narrow","choose","created","reorder","actionsFor","restore","removed","price","WxListScreen","WxCard","WxListDetail","_createElementVNode","_hoisted_1","WxInput","WxIcon","WxSelect","item","_normalizeClass","_createTextVNode","WxEmpty","WxSortableList","_withCtx","_openBlock","_resolveDynamicComponent","_hoisted_2","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_6","_hoisted_7","_renderList","chip","WxRowMenu","WxAlert","inline","back","TariffPane","tariffGroupsOptions","path","tariffs","options","TariffsPage","categoryRoutes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/price.ts","../src/TariffPane.vue","../src/TariffsPage.vue","../src/module.ts"],"sourcesContent":["import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { TariffDetail, TariffQuery, TariffRow, TariffsList } from './types'\n\nexport interface TariffsApi {\n /** Every tariff, with the groups the list can be narrowed to. */\n list(query?: TariffQuery): Promise<TariffsList>\n get(id: number): Promise<TariffDetail>\n /**\n * A new tariff out of the values of its form, in one request: the form is filled in first and\n * the record made on the first save, so nobody leaves an empty card behind.\n */\n create(values: ScreenModel): Promise<TariffDetail>\n /** Refused with a 422 under the name of the field — a line of the list under `features.<n>.text`. */\n save(id: number, values: ScreenModel): Promise<TariffDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<TariffRow>\n /** The order on screen: of the whole list, or — with `category` — of that group alone. */\n reorder(ids: number[], category?: number | null): Promise<void>\n}\n\n/** The path of the list under the panel's API, and of its order (`CategoryRoutes::items()`). */\nexport const TARIFFS_API = 'tariffs'\n\n/**\n * Everything under `/tariffs`, below the panel's API path — except the groups, which are the\n * panel's shared categories and are asked for with `createCategoriesApi(admin, 'tariffs/categories')`.\n */\nexport function createTariffsApi(admin: AdminContext): TariffsApi {\n const base = `${admin.apiPath}/${TARIFFS_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `category=` would ask for the tariffs of no group.\n if (query.search) search.set('search', query.search)\n if (query.category != null) search.set('category', String(query.category))\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<TariffsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: TariffDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: TariffDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: TariffDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n // The resource of the list line, and not the form's pair: a restored tariff stays closed.\n restore: (id) => admin.http.post<{ data: TariffRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids, category = null) => reorderItems(admin, TARIFFS_API, ids, category),\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-tariffs::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal.\n *\n * Categories in the code, groups in the words (decision 14): the `category` group holds what the\n * shared category screens say, and every one of its lines says \"group\".\n */\nexport const tariffsMessages: Record<string, Messages> = {\n module: {\n group: 'Tariffs',\n tariffs: 'Tariffs',\n groups: 'Groups',\n },\n tariff: {\n new: 'New tariff',\n untitled: 'Untitled',\n search: 'Search the tariffs',\n empty: 'No tariffs yet.',\n 'empty-help': 'A tariff reaches the site in a tariffs block, on any page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n trashed: 'Bin',\n 'filter-category': 'Group',\n 'any-category': 'Any group',\n 'not-published': 'Not published',\n featured: 'Recommended',\n 'no-price': 'No price',\n order: 'Drag to change the order of the cards on the site.',\n choose: 'Choose a tariff',\n 'choose-help': 'Or add a new one: the first line of the list.',\n save: 'Save',\n saved: 'The tariff is saved.',\n 'save-failed': 'The tariff was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete the tariff “:name”?',\n 'delete-text':\n 'It goes to the bin and leaves every block on the site. Restored, it comes back to its places.',\n deleted: 'The tariff is in the bin.',\n restore: 'Restore',\n restored: 'The tariff is back.',\n 'reorder-failed': 'The new order was not saved.',\n 'order-group':\n 'Drag to change the order inside this group. The rest of the list keeps its own.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you changed in this tariff is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n },\n screen: {\n tariff: 'Tariff',\n name: 'Name',\n 'name-help':\n 'Required in the default language. Not written in another one, it is shown as written in the default one.',\n badge: 'Badge',\n 'badge-help': 'A short line over the name: “30 HOURS / 25$”.',\n featured: 'Recommended',\n 'featured-help': 'The card stands out among the others.',\n pricing: 'Price',\n price: 'Price',\n 'price-help':\n 'A number, up to two digits after the point. Empty — the words below are shown instead.',\n currency: 'Currency',\n 'currency-help': 'The list comes from the site’s settings.',\n 'currency-placeholder': 'Choose a currency',\n period: 'Period',\n 'period-help': 'Printed after the price: “/mo”, “a year”.',\n 'price-text': 'Price in words',\n 'price-text-help': 'Printed when there is no price: “On request”, “Custom”.',\n features: 'What is included',\n 'features-help': 'A line not written in a language is left out of the list in that language.',\n 'features-add': 'Add a line',\n 'features-empty': 'No lines yet.',\n feature: 'Line',\n about: 'Description',\n description: 'Description',\n 'description-help': 'Shown only in the languages it is written in.',\n button: 'Button',\n 'button-label': 'Label',\n 'button-label-help': 'Without a label in a language, the card has no button in it.',\n 'button-link': 'Link',\n 'button-link-help':\n 'A page of the site or an address. A draft or a page in the bin hides the button.',\n 'button-variant': 'Look',\n 'button-variant-placeholder': 'Choose a look',\n settings: 'Settings',\n categories: 'Groups',\n 'categories-help': 'What a block picks the tariff by, and the tabs of a page of prices.',\n 'categories-add': 'Add to a group',\n 'categories-empty': 'In no group yet.',\n services: 'Services',\n 'services-help':\n 'The services this tariff is for: a tariffs block on a service’s page can show only its own.',\n published: 'Published',\n 'published-help':\n 'On the site in every block that shows its groups, from the moment it is saved.',\n },\n category: {\n 'field-title': 'Title',\n visible: 'Shown on the site',\n 'visible-help':\n 'Hidden, it drops out of every page of prices; its tariffs stay in the blocks that show them.',\n new: 'New group',\n empty: 'No groups yet.',\n 'empty-help':\n 'A group gathers tariffs — for individuals, for business: a block shows the ones you pick, and a page of prices can show each group as a tab.',\n order: 'Drag to change the order of the groups on the site.',\n hidden: 'Hidden from the site',\n tariffs: 'Tariffs: :count',\n 'show-tariffs': 'Show its tariffs',\n 'delete-blocked': 'This group still holds tariffs. Move them first.',\n 'delete-text': 'It goes to the bin and leaves every page of prices. Its tariffs stay.',\n deleted: 'The group is in the bin.',\n saved: 'The group is saved.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { tariffsMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useTariffsMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-tariffs', tariffsMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","import type { TariffRow } from './types'\n\n/**\n * The price of a row on one line, the way the offered block prints it: the symbol before the\n * number, the period after it (“$750 /mo”), or the words where there is no number (“On request”).\n * `''` when there is neither — the row says nothing rather than “0”.\n *\n * The number follows the same rule as the card's `amount` (§4.1): no fraction when it is whole,\n * two places when it is not, the separators of the panel's language. Zero is a number (decision\n * 16): “$0”, and “Free” is the words.\n */\nexport function priceLine(\n row: Pick<TariffRow, 'price' | 'symbol' | 'currency' | 'period' | 'price_text'>,\n locale: string,\n): string {\n if (row.price === null || row.price === undefined) return (row.price_text ?? '').trim()\n\n const whole = Number.isInteger(row.price)\n const amount = new Intl.NumberFormat(locale, {\n minimumFractionDigits: whole ? 0 : 2,\n maximumFractionDigits: whole ? 0 : 2,\n }).format(row.price)\n const symbol = row.symbol ?? row.currency ?? ''\n const period = (row.period ?? '').trim()\n\n return [`${symbol}${amount}`, period].filter((part) => part !== '').join(' ')\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createTariffsApi } from './api'\nimport { useTariffsMessages } from './i18n'\nimport type { TariffDetail, TariffSummary } from './types'\n\n/**\n * One tariff: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `tariffs.form` with a head over it and one button under it. One explicit\n * save, and no autosave: a tariff has no draft (decision 10), so what is saved is on the site at\n * once — the button is the moment somebody decides it is ready.\n *\n * A new tariff is a form first and a record on the first save: an empty row made by pressing\n * \"New tariff\" and walking away would be a tariff with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxTariffPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — a tariff not written yet. */\n id: number | null\n /** The group the list is narrowed to: a new tariff starts in it. */\n category?: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { category: null, inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The tariff was written; the list redraws its row. */\n saved: [tariff: TariffSummary]\n /** The first save of a new tariff: it is a record now, with an id to be opened by. */\n created: [tariff: TariffSummary]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createTariffsApi(context)\nconst locales = useLocales()\nuseTariffsMessages()\n\nconst t = useTranslate('webx-tariffs')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst tariff = ref<TariffSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('tariffs.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.name as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('tariff.new') : (tariff.value?.name ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('tariff.untitled')\n})\n\nfunction take(detail: TariffDetail): void {\n tariff.value = detail.tariff\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for a tariff with nothing in it yet. No currency: the panel does\n // not know the site's list, and the server gives a new tariff its first one (decision 15).\n const blank: ScreenModel = {\n name: {},\n badge: {},\n price: null,\n period: {},\n price_text: {},\n features: [],\n description: {},\n button_label: {},\n button_link: null,\n featured: false,\n published: false,\n categories: props.category === null ? [] : [props.category],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('tariff.saved'))\n if (made) emit('created', detail.tariff)\n else emit('saved', detail.tariff)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('tariff.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = tariff.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('tariff.delete-title', { name: record.name }),\n message: t('tariff.delete-text'),\n confirmText: t('tariff.delete'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('tariff.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', record.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('tariff.leave-title'),\n message: t('tariff.leave-text'),\n confirmText: t('tariff.leave'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another tariff is an update of it rather than a way\n * out — and so is every keystroke in the search above the list, which goes into the address too.\n * Only a change of the tariff asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.tariff === from.query.tariff ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the tariff. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && tariff.value !== null\n ? [\n {\n key: 'delete',\n label: t('tariff.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-tariff\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-tariff__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('tariff.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"tariff && !tariff.published\" #title-after>\n <wx-badge dot>{{ t('tariff.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"tariffs.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('tariff.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-tariff {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-tariff__ghost {\n max-width: 480px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createTariffsApi, TARIFFS_API } from './api'\nimport { useTariffsMessages } from './i18n'\nimport { priceLine } from './price'\nimport TariffPane from './TariffPane.vue'\nimport type { TariffGroupRef, TariffRow, TariffSummary } from './types'\n\n/**\n * The tariffs: all of them on the left, the one being written on the right (§5.2).\n *\n * The shape of the reviews, for the same reason: a price card is short, and the one next to edit\n * is usually the one beside it — \"Growth\" is priced against \"Starter\". On a phone the form is the\n * whole screen, in the drawer `WxListDetail` raises, and draws its own way back.\n *\n * Two orders, as with the reviews: without a filter a drag moves the common order; narrowed to\n * one group, the group's own; a search or the bin shows a selection, so the grips go away and the\n * line under the list says why. The query keeps the shared code's name, `category` (decision 14).\n *\n * Everything the screen is looking at lives in the address — the view, the group, the search and\n * the open tariff — so a link to a tariff is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/tariffs' })\n\nconst context = useAdmin()\nconst api = createTariffsApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseTariffsMessages()\n\nconst t = useTranslate('webx-tariffs')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<TariffRow[]>([])\nconst categories = ref<TariffGroupRef[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('tariffs.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'tariffs')?.title ??\n t('module.tariffs'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // A tariff from the list is not a tariff from the bin: the form closes with the view.\n delete query.tariff\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst category = computed<number | null>(() => {\n const raw = route.query.category\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open tariff: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.tariff\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"a tariff is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('tariff.all') },\n { value: 'trashed', label: t('tariff.trashed'), icon: 'trash' },\n])\n\nconst order = useItemOrder(TARIFFS_API, () => ({\n q: q.value,\n category: category.value,\n filtered: inBin.value,\n}))\n\n/* Dragged only by somebody who may write, and only in a list that is a whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\n/*\n * The panel's shared lines say \"category\" and \"items\"; here the editor reads \"group\" and \"cards\"\n * (decision 14). A locked list keeps the shared line: it explains why nothing moves.\n */\nconst hint = computed(() => {\n if (order.mode.value === 'category') return t('tariff.order-group')\n if (order.mode.value === 'all') return t('tariff.order')\n\n return order.hint.value\n})\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('tariff.empty-search')\n\n return inBin.value ? t('tariff.empty-bin') : t('tariff.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n search: q.value,\n category: inBin.value ? null : category.value,\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n categories.value = answer.filters.categories\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, category.value, q.value] as const,\n () => void load(),\n)\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(value: unknown): void {\n void router.replace({\n query: { ...route.query, category: typeof value === 'number' ? String(value) : undefined },\n })\n}\n\n/* `replace` and not `push`: going through twenty tariffs is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, tariff: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.tariff\n void router.replace({ query })\n}\n\n/** A new tariff was saved: it is a record now, and the list has it. */\nfunction created(tariff: TariffSummary): void {\n choose(tariff.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole — the list or the category's, whichever this is. The\n * list is already in its new order (`WxSortableList` reorders the model before it says so), so a\n * failure has to put it back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('tariff.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: TariffRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('tariff.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('tariff.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: TariffRow): Promise<void> {\n const agreed = await confirm({\n title: t('tariff.delete-title', { name: row.name }),\n message: t('tariff.delete-text'),\n confirmText: t('tariff.delete'),\n cancelText: t('tariff.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('tariff.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: TariffRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('tariff.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* The price as the site would print it — the language of the panel only decides the separators. */\nfunction price(row: TariffRow): string {\n return priceLine(row, context.i18n.state.locale)\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-tariffs\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-tariffs__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.tariffs')\"\n >\n <template #list>\n <div class=\"wx-tariffs__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-tariffs__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('tariff.search')\"\n :aria-label=\"t('tariff.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!-- Always in sight rather than behind a funnel: it is the one filter that decides\n which order a drag writes, and the list says so right under it. -->\n <wx-select\n v-if=\"!inBin\"\n :model-value=\"category\"\n :options=\"categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('tariff.any-category')\"\n :aria-label=\"t('tariff.filter-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow\"\n />\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new tariff is written where\n it will stand, beside the ones it joins — and the form opens the same way any other\n tariff's does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-tariffs__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('tariff.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-tariffs__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('tariff.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a tariff, and a row that both opens\n and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-tariffs__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: TariffRow) => item.name\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-tariff-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <span class=\"wx-tariff-row__body\">\n <span class=\"wx-tariff-row__who\">\n <span class=\"wx-tariff-row__name\">{{ item.name }}</span>\n <!-- A mark and not a control: the switch is in the form. -->\n <span\n v-if=\"item.featured\"\n class=\"wx-tariff-row__star\"\n role=\"img\"\n :aria-label=\"t('tariff.featured')\"\n >\n <wx-icon name=\"star\" size=\"sm\" />\n </span>\n </span>\n\n <span\n class=\"wx-tariff-row__price\"\n :class=\"{ 'is-missing': price(item) === '' }\"\n >\n {{ price(item) || t('tariff.no-price') }}\n </span>\n\n <span class=\"wx-tariff-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('tariff.not-published') }}\n </wx-badge>\n\n <span v-if=\"item.badge\" class=\"wx-tariff-row__badge\">{{ item.badge }}</span>\n\n <wx-badge v-for=\"chip in item.categories\" :key=\"chip.id\" size=\"sm\" round>\n {{ chip.title }}\n </wx-badge>\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.name\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-tariffs__note\"\n type=\"info\"\n :description=\"hint\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <tariff-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :category=\"category\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('tariff.choose')\"\n :description=\"canManage ? t('tariff.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-tariffs > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-tariffs__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-tariffs__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n/* The select is a fragment (it carries its dropdown beside it), so our scope attribute never\n reaches it: styled from our own wrapper (CLAUDE.md §4). */\n.wx-tariffs__bar > :deep(.wx-select) {\n flex: 1 1 140px;\n min-width: 0;\n}\n\n.wx-tariffs__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-tariffs__new:hover,\n.wx-tariffs__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-tariffs__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-tariffs__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-tariffs__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-tariffs__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-tariffs__list :deep(.wx-sortable-list__row:hover),\n.wx-tariffs__list :deep(.wx-sortable-list__row:has(.is-current)) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-tariffs__note {\n margin: var(--wx-space-8);\n}\n\n.wx-tariff-row {\n display: flex;\n align-items: flex-start;\n gap: var(--wx-space-10);\n /* The row is the target: a short name should not leave half the line dead — and a button,\n unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-tariff-row {\n cursor: pointer;\n}\n\n.wx-tariff-row__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--wx-space-2);\n min-width: 0;\n}\n\n.wx-tariff-row__who {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n column-gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-tariff-row__name {\n min-width: 0;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-tariff-row.is-current .wx-tariff-row__name {\n color: var(--wx-color-primary);\n}\n\n.wx-tariff-row__star {\n display: inline-flex;\n flex: none;\n align-self: center;\n color: var(--wx-color-warning);\n}\n\n/* The price is what tells two cards apart in the list, so it is the second line, not a chip. */\n.wx-tariff-row__price {\n overflow: hidden;\n font-size: var(--wx-font-size-sm);\n font-variant-numeric: tabular-nums;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.wx-tariff-row__price.is-missing {\n color: var(--wx-text-muted);\n font-style: italic;\n}\n\n.wx-tariff-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n margin-top: var(--wx-space-2);\n}\n\n.wx-tariff-row__facts:empty {\n display: none;\n}\n\n.wx-tariff-row__badge {\n min-width: 0;\n overflow: hidden;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n text-overflow: ellipsis;\n text-transform: uppercase;\n white-space: nowrap;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport TariffsPage from './TariffsPage.vue'\n\nexport interface TariffsOptions {\n /** Where the tariffs live inside the panel. The groups sit under it. */\n path?: string\n}\n\n/**\n * The groups of the tariffs as the panel's shared category screens see them. Exported so that a\n * panel mounting the screens somewhere of its own does not have to repeat the words.\n *\n * Categories in the code and groups in the words (decision 14): the API and the tables speak the\n * shared code's language, the editor reads \"group\" — “For individuals”, “For business”. No words\n * about addresses: a group has none (decision 2), and the shared list leaves the address line out\n * when the server says so.\n */\nexport function tariffGroupsOptions(path = '/tariffs'): CategoriesOptions {\n return {\n api: 'tariffs/categories',\n path: `${path}/groups`,\n name: 'webx.tariffs.groups',\n module: 'tariff-groups',\n screen: 'tariffs.category-form',\n manage: 'tariffs.groups.manage',\n count: 'tariffs_count',\n // The list narrowed to one group is also where its own order is dragged.\n items: (id) => ({ path, query: { category: String(id) } }),\n words: {\n new: 'webx-tariffs::category.new',\n empty: 'webx-tariffs::category.empty',\n 'empty-help': 'webx-tariffs::category.empty-help',\n order: 'webx-tariffs::category.order',\n hidden: 'webx-tariffs::category.hidden',\n count: 'webx-tariffs::category.tariffs',\n 'show-items': 'webx-tariffs::category.show-tariffs',\n 'delete-blocked': 'webx-tariffs::category.delete-blocked',\n 'delete-text': 'webx-tariffs::category.delete-text',\n deleted: 'webx-tariffs::category.deleted',\n saved: 'webx-tariffs::category.saved',\n 'field-title': 'webx-tariffs::category.field-title',\n },\n }\n}\n\n/**\n * The tariffs as sections of the panel: tariffs, and their groups (§5.2).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * both in the `tariffs` group, which is what draws them under one heading. The panel includes\n * them as `...tariffs()` — the line `extra.webx.panel.register` of the composer package names. A\n * section whose server half is not installed never appears — the entry is built from the manifest.\n */\nexport function tariffs(options: TariffsOptions = {}): AdminModule[] {\n const path = options.path ?? '/tariffs'\n\n return [\n {\n id: 'tariffs',\n path,\n // One route: the open tariff is in the address (`?tariff=`), beside the list it was opened\n // from, so a link to it is a link to both.\n routes: [{ path, name: 'webx.tariffs', component: TariffsPage, props: { base: path } }],\n },\n {\n id: 'tariff-groups',\n path: `${path}/groups`,\n routes: categoryRoutes(tariffGroupsOptions(path)),\n },\n ]\n}\n"],"names":["TARIFFS_API","createTariffsApi","admin","base","data","body","query","search","id","values","ids","category","reorderItems","tariffsMessages","seeded","useTariffsMessages","i18n","useAdmin","priceLine","row","locale","whole","amount","symbol","period","part","props","__props","emit","__emit","context","api","locales","useLocales","t","useTranslate","message","useErrorText","tariff","ref","snapshot","loading","saving","errors","canManage","computed","current","dirty","state","title","written","fallback","localizedValue","take","detail","blank","load","error","toast","save","made","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","_createElementBlock","_createBlock","_unref","WxSkeleton","_Fragment","_createVNode","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","WxButton","route","useRoute","router","useRouter","rows","categories","module","view","value","inBin","raw","q","typed","selected","open","close","views","order","useItemOrder","sortable","hint","emptyText","answer","watch","pause","narrow","choose","created","reorder","actionsFor","restore","removed","price","WxListScreen","WxCard","WxListDetail","_createElementVNode","_hoisted_1","WxInput","WxIcon","WxSelect","item","_normalizeClass","_createTextVNode","WxEmpty","WxSortableList","_withCtx","_openBlock","_resolveDynamicComponent","_hoisted_2","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_6","_hoisted_7","_renderList","chip","WxRowMenu","WxAlert","inline","back","TariffPane","tariffGroupsOptions","path","tariffs","options","TariffsPage","categoryRoutes"],"mappings":"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"}
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.wx-tariff[data-v-
|
|
1
|
+
.wx-tariff[data-v-196f1a0d]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-width:0;padding:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-tariff__ghost[data-v-196f1a0d]{max-width:480px}.wx-tariffs>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-tariffs__bar[data-v-d86b91c1]{display:flex;flex-wrap:wrap;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-12) var(--wx-space-8)}.wx-tariffs__search[data-v-d86b91c1]{flex:1 1 180px;min-width:0}.wx-tariffs__bar[data-v-d86b91c1]>.wx-select{flex:1 1 140px;min-width:0}.wx-tariffs__new[data-v-d86b91c1]{display:flex;align-items:center;gap:var(--wx-space-8);width:calc(100% - 2 * var(--wx-space-8));margin:0 var(--wx-space-8) var(--wx-space-4);padding:var(--wx-space-8);border:1px dashed var(--wx-border-default);border-radius:var(--wx-radius-sm);background:none;font:inherit;color:var(--wx-color-primary);text-align:start;cursor:pointer}.wx-tariffs__new[data-v-d86b91c1]:hover,.wx-tariffs__new.is-current[data-v-d86b91c1]{background:var(--wx-bg-subtle)}.wx-tariffs__loading[data-v-d86b91c1]{padding:var(--wx-space-12)}.wx-tariffs__list[data-v-d86b91c1]{padding-inline:var(--wx-space-8)}.wx-tariffs__list.is-loading[data-v-d86b91c1]{opacity:.6}.wx-tariffs__list[data-v-d86b91c1] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4);border-radius:var(--wx-radius-sm)}.wx-tariffs__list[data-v-d86b91c1] .wx-sortable-list__row:hover,.wx-tariffs__list[data-v-d86b91c1] .wx-sortable-list__row:has(.is-current){background:var(--wx-bg-subtle)}.wx-tariffs__note[data-v-d86b91c1]{margin:var(--wx-space-8)}.wx-tariff-row[data-v-d86b91c1]{display:flex;align-items:flex-start;gap:var(--wx-space-10);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start}button.wx-tariff-row[data-v-d86b91c1]{cursor:pointer}.wx-tariff-row__body[data-v-d86b91c1]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-2);min-width:0}.wx-tariff-row__who[data-v-d86b91c1]{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--wx-space-8);min-width:0}.wx-tariff-row__name[data-v-d86b91c1]{min-width:0;overflow:hidden;font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}.wx-tariff-row.is-current .wx-tariff-row__name[data-v-d86b91c1]{color:var(--wx-color-primary)}.wx-tariff-row__star[data-v-d86b91c1]{display:inline-flex;flex:none;align-self:center;color:var(--wx-color-warning)}.wx-tariff-row__price[data-v-d86b91c1]{overflow:hidden;font-size:var(--wx-font-size-sm);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap}.wx-tariff-row__price.is-missing[data-v-d86b91c1]{color:var(--wx-text-muted);font-style:italic}.wx-tariff-row__facts[data-v-d86b91c1]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-4);min-width:0;margin-top:var(--wx-space-2)}.wx-tariff-row__facts[data-v-d86b91c1]:empty{display:none}.wx-tariff-row__badge[data-v-d86b91c1]{min-width:0;overflow:hidden;color:var(--wx-text-muted);font-size:var(--wx-font-size-xs);text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webx-ui/module-tariffs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Tariffs for the WebX UI admin panel: price cards with a currency, a period, what is included and a button, in groups, ordered by hand, shown on the site by a block.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"vue-router": "^4.5.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@webx-ui/core": "^0.34.
|
|
48
|
-
"@webx-ui/module-admin": "^0.
|
|
49
|
-
"@webx-ui/schema": "^0.6.
|
|
47
|
+
"@webx-ui/core": "^0.34.1",
|
|
48
|
+
"@webx-ui/module-admin": "^0.19.0",
|
|
49
|
+
"@webx-ui/schema": "^0.6.4"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^24.10.1",
|