@saasicat/ui-vue 0.7.0 → 0.9.0

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.
Files changed (41) hide show
  1. package/dist/{chunk-A3W2N26K.js → chunk-66BUSVC5.js} +68 -217
  2. package/dist/{chunk-X5SQ5HMB.js → chunk-TBLXKSTR.js} +0 -137
  3. package/dist/{chunk-QZOSDCNP.js → chunk-TY2WYKME.js} +1 -1
  4. package/dist/client/index.cjs +180 -463
  5. package/dist/client/index.d.cts +32 -57
  6. package/dist/client/index.d.ts +32 -57
  7. package/dist/client/index.js +4 -6
  8. package/dist/index.cjs +273 -580
  9. package/dist/index.d.cts +10 -55
  10. package/dist/index.d.ts +10 -55
  11. package/dist/index.js +76 -101
  12. package/dist/{messages-7b0P8W75.d.ts → messages-BIYw32u1.d.cts} +0 -67
  13. package/dist/{messages-7b0P8W75.d.cts → messages-BIYw32u1.d.ts} +0 -67
  14. package/dist/quasar/index.cjs +0 -136
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +2 -2
  18. package/dist/{use-super-admin-i18n-B3v3-SWZ.d.ts → use-super-admin-i18n-CWNu_W3u.d.ts} +1 -1
  19. package/dist/{use-super-admin-i18n-DMktRVEt.d.cts → use-super-admin-i18n-DL_qI1BY.d.cts} +1 -1
  20. package/package.json +5 -5
  21. package/src/client/admin-resource-client.ts +112 -0
  22. package/src/client/i18n/messages/nav.ts +0 -2
  23. package/src/client/i18n/messages/plan-versions.ts +0 -143
  24. package/src/client/index.ts +1 -2
  25. package/src/client/nav-builder.ts +9 -4
  26. package/src/index.ts +0 -1
  27. package/src/pages-standard/AdminLayout.vue +1 -1
  28. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +1 -1
  29. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +1 -1
  30. package/src/pages-standard/plan-versions/catalog-history.types.ts +37 -0
  31. package/src/pages-standard/plan-versions/format.ts +2 -38
  32. package/src/client/plan-versions-catalog.ts +0 -380
  33. package/src/pages-standard/PlanVersionsPage.vue +0 -220
  34. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +0 -309
  35. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +0 -48
  36. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +0 -326
  37. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +0 -65
  38. package/src/pages-standard/plan-versions/PlanVersionsList.vue +0 -315
  39. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +0 -261
  40. package/src/pages-standard/plan-versions/types.ts +0 -33
  41. package/src/vue/use-plan-versions.ts +0 -83
@@ -38,7 +38,7 @@ __export(client_exports, {
38
38
  SA_MESSAGES: () => SA_MESSAGES,
39
39
  buildRoutes: () => buildRoutes,
40
40
  buildSidebar: () => buildSidebar,
41
- buildSnapshots: () => buildSnapshots,
41
+ createAdminResourceClient: () => createAdminResourceClient,
42
42
  defaultHttpClient: () => defaultHttpClient,
43
43
  defaultKvStore: () => defaultKvStore,
44
44
  defaultSectionOrder: () => defaultSectionOrder,
@@ -46,7 +46,6 @@ __export(client_exports, {
46
46
  formatCurrency: () => formatCurrency,
47
47
  formatMessage: () => formatMessage,
48
48
  getJson: () => getJson,
49
- listOpenDrafts: () => listOpenDrafts,
50
49
  mergeMessages: () => mergeMessages,
51
50
  postJson: () => postJson,
52
51
  resolveExtension: () => resolveExtension,
@@ -274,7 +273,6 @@ var navMessages = defineMessages(
274
273
  subscriptions: "Abonnements",
275
274
  promoCodes: "Promo-Codes",
276
275
  plans: "Pl\xE4ne & Versionen",
277
- planVersions: "Plan-Versionen",
278
276
  audit: "Audit-Log",
279
277
  users: "Benutzer",
280
278
  pilots: "Piloten",
@@ -298,7 +296,6 @@ var navMessages = defineMessages(
298
296
  subscriptions: "Subscriptions",
299
297
  promoCodes: "Promo codes",
300
298
  plans: "Plans & versions",
301
- planVersions: "Plan versions",
302
299
  audit: "Audit log",
303
300
  users: "Users",
304
301
  pilots: "Pilots",
@@ -324,7 +321,6 @@ var DEFAULT_STANDARD_PAGE_ROUTES = {
324
321
  subscriptions: "/admin/subscriptions",
325
322
  promoCodes: "/admin/promo-codes",
326
323
  plans: "/admin/plans",
327
- planVersions: "/admin/plan-versions",
328
324
  audit: "/admin/audit",
329
325
  users: "/admin/users",
330
326
  pilots: "/admin/pilots",
@@ -334,13 +330,15 @@ var DEFAULT_STANDARD_PAGE_ROUTES = {
334
330
  platformEmail: "/admin/platform-email",
335
331
  platformEmailHistory: "/admin/platform-email-history"
336
332
  };
333
+ function isSupportedStandardPageKey(key) {
334
+ return Object.hasOwn(DEFAULT_STANDARD_PAGE_ROUTES, key);
335
+ }
337
336
  var DEFAULT_ICONS = {
338
337
  dashboard: "dashboard",
339
338
  tenants: "business",
340
339
  subscriptions: "card_membership",
341
340
  promoCodes: "local_activity",
342
341
  plans: "workspace_premium",
343
- planVersions: "history_edu",
344
342
  audit: "history",
345
343
  users: "people",
346
344
  pilots: "flight_takeoff",
@@ -355,7 +353,6 @@ var PAGE_SECTIONS = {
355
353
  discovery: "catalog",
356
354
  bundles: "catalog",
357
355
  plans: "catalog",
358
- planVersions: "catalog",
359
356
  marketingCatalog: "catalog",
360
357
  promoCodes: "catalog",
361
358
  tenants: "customers",
@@ -383,6 +380,7 @@ function buildRoutes(manifest, options = {}) {
383
380
  const overrideIcons = { ...DEFAULT_ICONS, ...options.standardPageIcons };
384
381
  const overrideNavSection = { ...defaultNavSections, ...options.standardPageNavSection };
385
382
  for (const key of Object.keys(standard)) {
383
+ if (!isSupportedStandardPageKey(key)) continue;
386
384
  const def = standard[key];
387
385
  if (!def?.enabled) continue;
388
386
  const cap = def.requiredCapability ?? null;
@@ -623,471 +621,83 @@ var BatchColumnFetcher = class {
623
621
  }
624
622
  };
625
623
 
626
- // src/client/i18n/format.ts
627
- function formatMessage(template, params) {
628
- return template.replace(/\{(\w+)\}/g, (match, name) => {
629
- const value = params[name];
630
- return value === void 0 ? match : String(value);
631
- });
632
- }
633
-
634
- // src/client/i18n/messages/plan-versions.ts
635
- var planVersionsMessages = defineMessages(
636
- {
637
- compareEnd: "Vergleich beenden",
638
- page: {
639
- loadingCatalog: "Catalog wird geladen\u2026",
640
- noAuditLoader: "Kein Audit-Loader konfiguriert."
641
- },
642
- header: {
643
- modeList: "Plan-Liste",
644
- modeMatrix: "Feature-Matrix",
645
- modeAudit: "Aktivit\xE4t",
646
- publishedOn: "Publiziert {date}",
647
- openDrafts: "offene Drafts",
648
- regressionOne: "Regression",
649
- regressionMany: "Regressionen",
650
- startPublishFlow: "Publish-Flow starten",
651
- compareModeHint: "Vergleichsmodus aktiv \u2014 andere Ansichten deaktiviert"
652
- },
653
- status: {
654
- draft: "DRAFT",
655
- active: "AKTIV",
656
- archived: "ARCHIV"
657
- },
658
- timeline: {
659
- title: "Plan-Catalog",
660
- snapshotCount: "{count} Snapshots",
661
- noOpenDrafts: "keine offenen Drafts",
662
- openCount: "{count} offen",
663
- unknown: "unbekannt",
664
- compareHint: "Rechtsklick auf Snapshot \u2192 vergleichen"
665
- },
666
- list: {
667
- kpiPlans: "Pakete",
668
- kpiSelfService: "Self-Service",
669
- kpiOfTotal: "von {count}",
670
- kpiFeaturesTotal: "Features gesamt",
671
- missingPlansLabel: "Fehlende Pakete:",
672
- missingPlansHint: "\u2014 keine Live-Version in diesem Snapshot.",
673
- createDraftFor: "Draft f\xFCr {planId}",
674
- colPlan: "Paket",
675
- colPriceNet: "Preis (netto)",
676
- colFeatures: "Features",
677
- draftHintLabel: "Arbeitsstand:",
678
- draftHintBefore: "Drafts oben in der Liste. Limits, Preise und Features per Klick auf",
679
- draftHintEditButton: '\u201EBearbeiten"',
680
- draftHintMiddle: "\xE4ndern (ohne MFA). Erst",
681
- draftHintPublishFlow: "Publish-Flow",
682
- draftHintAfter: "(Validate \u2192 Diff-Review \u2192 MFA \u2192 Apply) macht sie f\xFCr k\xFCnftige Renewals wirksam."
683
- },
684
- matrix: {
685
- colFeature: "Feature",
686
- perMonthShort: "/Mo",
687
- fallbackGroup: "Sonstige"
688
- },
689
- diff: {
690
- legend: "Vergleich",
691
- plansChanged: "Pakete ge\xE4ndert",
692
- regressions: "Regressionen",
693
- sectionPlans: "Pakete",
694
- changeCountOne: "{count} \xC4nderung",
695
- changeCountMany: "{count} \xC4nderungen"
696
- },
697
- diffPreview: {
698
- empty: "Keine \xC4nderungen gegen\xFCber der Vorg\xE4ngerversion.",
699
- improvement: "Verbesserung",
700
- regression: "Verschlechterung",
701
- neutral: "Neutral"
702
- },
703
- diffFields: {
704
- featuresAdded: "Hinzugef\xFCgte Features",
705
- featuresRemoved: "Entfernte Features",
706
- maxUsers: "Max. Benutzer",
707
- maxStorageGb: "Speicher (GB)",
708
- monthlyNet: "Preis monatlich (netto)",
709
- yearlyNet: "Preis j\xE4hrlich (netto)",
710
- unitSize: "Einheitsgr\xF6\xDFe"
711
- },
712
- audit: {
713
- title: "Aktivit\xE4t",
714
- eventCount: "{count} Events",
715
- loading: "Audit-Trail wird geladen\u2026",
716
- empty: "Keine Plan-/Add-on-Events im Audit-Log gefunden.",
717
- actionPlanPublished: "Plan publiziert",
718
- actionPlanDraftCreated: "Plan-Draft angelegt",
719
- actionPlanDraftUpdated: "Plan-Draft ge\xE4ndert",
720
- actionPlanDraftDeleted: "Plan-Draft gel\xF6scht",
721
- actionCatalogPublished: "Catalog publiziert",
722
- detailRegression: "Mit mindestens einer Verschlechterung publiziert.",
723
- detailFieldsChanged: "{count} Felder ge\xE4ndert."
724
- },
725
- bulkPublish: {
726
- changeNoteRequired: "changeNote ist Pflicht beim Publish."
727
- },
728
- format: {
729
- priceIndividual: "individuell",
730
- today: "heute",
731
- yesterday: "gestern",
732
- daysAgo: "vor {count} Tagen",
733
- weeksAgoOne: "vor {count} Woche",
734
- weeksAgoMany: "vor {count} Wochen",
735
- monthsAgo: "vor {count} Monaten",
736
- yearsAgoOne: "vor {count} Jahr",
737
- yearsAgoMany: "vor {count} Jahren"
738
- },
739
- catalog: {
740
- draftsLabel: "Arbeitsstand",
741
- draftsTitleEmpty: "Keine offenen Drafts",
742
- draftsTitleOne: "{count} offener Draft bereit f\xFCr Publish",
743
- draftsTitleMany: "{count} offene Drafts bereit f\xFCr Publish",
744
- draftsDescriptionEmpty: "Lege einen Draft an, um Limits, Preise oder Features zu \xE4ndern. Drafts sind ohne MFA editierbar und werden erst beim Publish wirksam.",
745
- draftsDescription: "Diese Tabelle zeigt, wie der Catalog nach Publish aller Drafts aussehen w\xFCrde. Bestehende Mandanten bleiben auf der vorherigen Version, bis sie aktiv migriert werden.",
746
- activeLabel: "Aktuell",
747
- activeTitle: "Live-Catalog",
748
- activeDescription: "Diese Plan-Versionen sind aktuell f\xFCr neue Onboardings und f\xFCr Renewals nach Stichtag aktiv.",
749
- publishEvent: "Publish-Event",
750
- publishEventSingle: "{planId} v{version} publiziert",
751
- publishEventMultiple: "{count} Versionen publiziert",
752
- reconstructedSnapshot: "Snapshot rekonstruiert aus per-Entity-Versionen."
753
- }
754
- },
755
- {
756
- compareEnd: "End comparison",
757
- page: {
758
- loadingCatalog: "Loading catalog\u2026",
759
- noAuditLoader: "No audit loader configured."
760
- },
761
- header: {
762
- modeList: "Plan list",
763
- modeMatrix: "Feature matrix",
764
- modeAudit: "Activity",
765
- publishedOn: "Published {date}",
766
- openDrafts: "open drafts",
767
- regressionOne: "regression",
768
- regressionMany: "regressions",
769
- startPublishFlow: "Start publish flow",
770
- compareModeHint: "Comparison mode active \u2014 other views disabled"
771
- },
772
- status: {
773
- draft: "DRAFT",
774
- active: "ACTIVE",
775
- archived: "ARCHIVED"
776
- },
777
- timeline: {
778
- title: "Plan catalog",
779
- snapshotCount: "{count} snapshots",
780
- noOpenDrafts: "no open drafts",
781
- openCount: "{count} open",
782
- unknown: "unknown",
783
- compareHint: "Right-click a snapshot \u2192 compare"
784
- },
785
- list: {
786
- kpiPlans: "Plans",
787
- kpiSelfService: "Self-service",
788
- kpiOfTotal: "of {count}",
789
- kpiFeaturesTotal: "Features total",
790
- missingPlansLabel: "Missing plans:",
791
- missingPlansHint: "\u2014 no live version in this snapshot.",
792
- createDraftFor: "Draft for {planId}",
793
- colPlan: "Plan",
794
- colPriceNet: "Price (net)",
795
- colFeatures: "Features",
796
- draftHintLabel: "Working state:",
797
- draftHintBefore: "Drafts at the top of the list. Change limits, prices and features by clicking",
798
- draftHintEditButton: '"Edit"',
799
- draftHintMiddle: "(without MFA). Only the",
800
- draftHintPublishFlow: "publish flow",
801
- draftHintAfter: "(validate \u2192 diff review \u2192 MFA \u2192 apply) makes them effective for future renewals."
802
- },
803
- matrix: {
804
- colFeature: "Feature",
805
- perMonthShort: "/mo",
806
- fallbackGroup: "Other"
807
- },
808
- diff: {
809
- legend: "Comparison",
810
- plansChanged: "Plans changed",
811
- regressions: "Regressions",
812
- sectionPlans: "Plans",
813
- changeCountOne: "{count} change",
814
- changeCountMany: "{count} changes"
815
- },
816
- diffPreview: {
817
- empty: "No changes compared to the previous version.",
818
- improvement: "Improvement",
819
- regression: "Regression",
820
- neutral: "Neutral"
821
- },
822
- diffFields: {
823
- featuresAdded: "Added features",
824
- featuresRemoved: "Removed features",
825
- maxUsers: "Max. users",
826
- maxStorageGb: "Storage (GB)",
827
- monthlyNet: "Price monthly (net)",
828
- yearlyNet: "Price yearly (net)",
829
- unitSize: "Unit size"
830
- },
831
- audit: {
832
- title: "Activity",
833
- eventCount: "{count} events",
834
- loading: "Loading audit trail\u2026",
835
- empty: "No plan/add-on events found in the audit log.",
836
- actionPlanPublished: "Plan published",
837
- actionPlanDraftCreated: "Plan draft created",
838
- actionPlanDraftUpdated: "Plan draft changed",
839
- actionPlanDraftDeleted: "Plan draft deleted",
840
- actionCatalogPublished: "Catalog published",
841
- detailRegression: "Published with at least one regression.",
842
- detailFieldsChanged: "{count} fields changed."
843
- },
844
- bulkPublish: {
845
- changeNoteRequired: "changeNote is required when publishing."
846
- },
847
- format: {
848
- priceIndividual: "custom",
849
- today: "today",
850
- yesterday: "yesterday",
851
- daysAgo: "{count} days ago",
852
- weeksAgoOne: "{count} week ago",
853
- weeksAgoMany: "{count} weeks ago",
854
- monthsAgo: "{count} months ago",
855
- yearsAgoOne: "{count} year ago",
856
- yearsAgoMany: "{count} years ago"
857
- },
858
- catalog: {
859
- draftsLabel: "Working state",
860
- draftsTitleEmpty: "No open drafts",
861
- draftsTitleOne: "{count} open draft ready to publish",
862
- draftsTitleMany: "{count} open drafts ready to publish",
863
- draftsDescriptionEmpty: "Create a draft to change limits, prices or features. Drafts are editable without MFA and only take effect when published.",
864
- draftsDescription: "This table shows what the catalog would look like after publishing all drafts. Existing tenants stay on the previous version until they are actively migrated.",
865
- activeLabel: "Current",
866
- activeTitle: "Live catalog",
867
- activeDescription: "These plan versions are currently active for new onboardings and for renewals after the effective date.",
868
- publishEvent: "Publish event",
869
- publishEventSingle: "{planId} v{version} published",
870
- publishEventMultiple: "{count} versions published",
871
- reconstructedSnapshot: "Snapshot reconstructed from per-entity versions."
872
- }
873
- }
874
- );
875
-
876
- // src/client/plan-versions-catalog.ts
877
- var dec = (s) => Number(s);
878
- function extractQuotas(p) {
879
- if (p.quotas && Object.keys(p.quotas).length > 0) {
880
- return { ...p.quotas };
881
- }
882
- const out = {};
883
- if (typeof p.maxUsers === "number") out.users = p.maxUsers;
884
- if (typeof p.maxVehicles === "number") out.vehicles = p.maxVehicles;
885
- if (typeof p.maxStorageGb === "number") out.storageGb = p.maxStorageGb;
886
- return out;
887
- }
888
- var sortByPublishedDesc = (rows) => [...rows].sort((a, b) => {
889
- const at = a.publishedAt ? Date.parse(a.publishedAt) : Number.POSITIVE_INFINITY;
890
- const bt = b.publishedAt ? Date.parse(b.publishedAt) : Number.POSITIVE_INFINITY;
891
- if (at !== bt) return bt - at;
892
- return b.version - a.version;
893
- });
894
- function findLiveAt(rows, asOfMs) {
895
- const candidates = rows.filter((r) => {
896
- if (r.publishedAt === null) return false;
897
- if (Date.parse(r.publishedAt) > asOfMs) return false;
898
- if (r.supersededAt !== null && Date.parse(r.supersededAt) <= asOfMs) return false;
899
- return true;
900
- });
901
- if (candidates.length === 0) return null;
902
- return [...candidates].sort((a, b) => b.version - a.version)[0] ?? null;
903
- }
904
- function findCurrentLive(rows) {
905
- return rows.find((r) => r.publishedAt !== null && r.supersededAt === null) ?? null;
906
- }
907
- function findDraft(rows) {
908
- return rows.find((r) => r.publishedAt === null) ?? null;
909
- }
910
- function groupBy(rows, keyOf) {
911
- const out = /* @__PURE__ */ new Map();
912
- for (const r of rows) {
913
- const k = keyOf(r);
914
- const list = out.get(k) ?? [];
915
- list.push(r);
916
- out.set(k, list);
917
- }
918
- return out;
919
- }
920
- function resolvePlan(source, liveBase) {
921
- const quotas = extractQuotas(source);
922
- return {
923
- source,
924
- liveBase,
925
- isDraft: source.publishedAt === null,
926
- planId: source.planId,
927
- features: source.features,
928
- quotas,
929
- monthlyNet: dec(source.monthlyNet),
930
- yearlyNet: dec(source.yearlyNet),
931
- marketed: source.marketed,
932
- version: source.version,
933
- maxUsers: quotas.users,
934
- maxVehicles: quotas.vehicles,
935
- maxStorageGb: quotas.storageGb
624
+ // src/client/admin-resource-client.ts
625
+ function createAdminResourceClient(options) {
626
+ const base = options.adminBase ?? "/api/v1/admin";
627
+ const tenantsEndpoint = `${base}/tenants`;
628
+ const getJson2 = async (url) => {
629
+ const response = await options.http(url, { method: "GET" });
630
+ assertSuccess(response.status, "GET", url);
631
+ return await response.json();
936
632
  };
937
- }
938
- function draftsTitle(draftCount, msg) {
939
- if (draftCount === 0) return msg.draftsTitleEmpty;
940
- const template = draftCount === 1 ? msg.draftsTitleOne : msg.draftsTitleMany;
941
- return formatMessage(template, { count: draftCount });
942
- }
943
- function dateLabel(iso, locale) {
944
- return new Date(iso).toLocaleDateString(SA_INTL_LOCALES[locale], {
945
- day: "2-digit",
946
- month: "short",
947
- year: "numeric"
948
- });
949
- }
950
- function buildDraftsSnapshot(data, sortPlansFn, locale) {
951
- const msg = planVersionsMessages[locale].catalog;
952
- const planByKey = groupBy(data.planVersions, (r) => r.planId);
953
- const plans = [];
954
- for (const [, rows] of planByKey) {
955
- const draft = findDraft(rows);
956
- const live = findCurrentLive(rows);
957
- if (draft) plans.push(resolvePlan(draft, live));
958
- else if (live) plans.push(resolvePlan(live, null));
959
- }
960
- const draftCount = plans.filter((p) => p.isDraft).length;
961
- return {
962
- id: "drafts",
963
- kind: "drafts",
964
- status: "DRAFT",
965
- label: msg.draftsLabel,
966
- title: draftsTitle(draftCount, msg),
967
- description: draftCount === 0 ? msg.draftsDescriptionEmpty : msg.draftsDescription,
968
- asOf: null,
969
- createdAt: null,
970
- publishedAt: null,
971
- authorEmail: null,
972
- plans: sortPlansFn(plans),
973
- draftCount,
974
- regressionCount: 0
633
+ const sendJson = async (method, url, body) => {
634
+ const response = await options.http(url, {
635
+ method,
636
+ headers: { "content-type": "application/json" },
637
+ body: body === void 0 ? void 0 : JSON.stringify(body)
638
+ });
639
+ assertSuccess(response.status, method, url);
640
+ return await response.json();
975
641
  };
976
- }
977
- function buildActiveSnapshot(data, sortPlansFn, locale) {
978
- const msg = planVersionsMessages[locale].catalog;
979
- const planByKey = groupBy(data.planVersions, (r) => r.planId);
980
- const plans = [];
981
- for (const [, rows] of planByKey) {
982
- const live = findCurrentLive(rows);
983
- if (live) plans.push(resolvePlan(live, null));
984
- }
985
- if (plans.length === 0) return null;
986
- const publishedDates = plans.map((p) => p.source.publishedAt).filter((t) => t !== null).sort();
987
- const latestPublished = publishedDates[publishedDates.length - 1];
988
642
  return {
989
- id: "active",
990
- kind: "active",
991
- status: "ACTIVE",
992
- label: msg.activeLabel,
993
- title: msg.activeTitle,
994
- description: msg.activeDescription,
995
- asOf: latestPublished ?? null,
996
- createdAt: null,
997
- publishedAt: latestPublished ?? null,
998
- authorEmail: null,
999
- plans: sortPlansFn(plans),
1000
- draftCount: 0,
1001
- regressionCount: 0
643
+ tenantsEndpoint,
644
+ loadTenantDetail: (slug) => getJson2(`${tenantsEndpoint}/${encodeURIComponent(slug)}`),
645
+ loadUsers: (filter) => getJson2(
646
+ `${base}/users${queryString({ q: filter.q, tenant: filter.tenant })}`
647
+ ),
648
+ loadAudit: (filter) => getJson2(
649
+ `${base}/audit${queryString({
650
+ actor: filter.actor,
651
+ action: filter.action,
652
+ entity: filter.entity,
653
+ since: filter.since,
654
+ limit: filter.limit
655
+ })}`
656
+ ),
657
+ loadSubscriptions: () => getJson2(`${base}/subscriptions`),
658
+ loadPromos: (filter) => getJson2(
659
+ `${base}/promo-codes${queryString({
660
+ search: filter.search,
661
+ status: filter.status
662
+ })}`
663
+ ),
664
+ createPromo: async (payload) => {
665
+ await sendJson("POST", `${base}/promo-codes`, payload);
666
+ },
667
+ updatePromo: async (id, payload) => {
668
+ await sendJson("PATCH", `${base}/promo-codes/${encodeURIComponent(id)}`, payload);
669
+ },
670
+ deletePromo: async (id) => {
671
+ await sendJson("DELETE", `${base}/promo-codes/${encodeURIComponent(id)}`);
672
+ },
673
+ suspendTenant: (slug, reason) => sendJson("POST", `${tenantsEndpoint}/${encodeURIComponent(slug)}/suspend`, {
674
+ reason
675
+ }),
676
+ reactivateTenant: (slug) => sendJson("POST", `${tenantsEndpoint}/${encodeURIComponent(slug)}/reactivate`)
1002
677
  };
1003
678
  }
1004
- function buildHistoricalSnapshots(data, sortPlansFn, locale) {
1005
- const msg = planVersionsMessages[locale].catalog;
1006
- const allPublishedAt = /* @__PURE__ */ new Set();
1007
- for (const r of data.planVersions) if (r.publishedAt) allPublishedAt.add(r.publishedAt);
1008
- if (allPublishedAt.size === 0) return [];
1009
- const planByKey = groupBy(data.planVersions, (r) => r.planId);
1010
- const liveTimestamps = /* @__PURE__ */ new Set();
1011
- for (const rows of planByKey.values()) {
1012
- const live = findCurrentLive(rows);
1013
- if (live?.publishedAt) liveTimestamps.add(live.publishedAt);
679
+ function assertSuccess(status, method, url) {
680
+ if (status < 200 || status >= 300) {
681
+ throw new Error(`${method} ${url} \u2192 HTTP ${status}`);
1014
682
  }
1015
- const liveSorted = [...liveTimestamps].sort();
1016
- const latestLive = liveSorted[liveSorted.length - 1];
1017
- const sortedDesc = [...allPublishedAt].sort((a, b) => Date.parse(b) - Date.parse(a));
1018
- const out = [];
1019
- for (const ts of sortedDesc) {
1020
- if (latestLive && Date.parse(ts) >= Date.parse(latestLive)) continue;
1021
- const tMs = Date.parse(ts) + 1;
1022
- const plans = [];
1023
- for (const [, rows] of planByKey) {
1024
- const live = findLiveAt(rows, tMs);
1025
- if (live) plans.push(resolvePlan(live, null));
1026
- }
1027
- if (plans.length === 0) continue;
1028
- const publishedAtThisEvent = data.planVersions.filter((r) => r.publishedAt === ts);
1029
- const regressionCount = publishedAtThisEvent.filter((r) => !r.nonRegressive).length;
1030
- const author = publishedAtThisEvent[0]?.publishedByUserId ?? null;
1031
- out.push({
1032
- id: `hist-${ts}`,
1033
- kind: "historical",
1034
- status: "ARCHIVED",
1035
- label: dateLabel(ts, locale),
1036
- title: summarizeEvent(publishedAtThisEvent, msg),
1037
- description: detailEvent(publishedAtThisEvent, msg),
1038
- asOf: ts,
1039
- createdAt: ts,
1040
- publishedAt: ts,
1041
- authorEmail: author,
1042
- plans: sortPlansFn(plans),
1043
- draftCount: 0,
1044
- regressionCount
1045
- });
1046
- }
1047
- return out;
1048
683
  }
1049
- function summarizeEvent(rows, msg) {
1050
- if (rows.length === 0) return msg.publishEvent;
1051
- if (rows.length === 1) {
1052
- const r = rows[0];
1053
- return formatMessage(msg.publishEventSingle, { planId: r.planId, version: r.version });
684
+ function queryString(params) {
685
+ const search = new URLSearchParams();
686
+ for (const [key, value2] of Object.entries(params)) {
687
+ if (value2 === void 0 || value2 === null || value2 === "") continue;
688
+ search.set(key, String(value2));
1054
689
  }
1055
- return formatMessage(msg.publishEventMultiple, { count: rows.length });
1056
- }
1057
- function detailEvent(rows, msg) {
1058
- if (rows.length === 0) return "";
1059
- const note = rows[0]?.changeNote ?? "";
1060
- return note.length > 0 ? note : msg.reconstructedSnapshot;
690
+ const value = search.toString();
691
+ return value ? `?${value}` : "";
1061
692
  }
1062
- function sortPlansBy(order) {
1063
- if (!order || order.length === 0) {
1064
- return (rows) => [...rows].sort((a, b) => a.planId.localeCompare(b.planId));
1065
- }
1066
- const idx = {};
1067
- for (let i = 0; i < order.length; i++) idx[order[i]] = i;
1068
- return (rows) => [...rows].sort((a, b) => {
1069
- const ai = idx[a.planId] ?? Number.POSITIVE_INFINITY;
1070
- const bi = idx[b.planId] ?? Number.POSITIVE_INFINITY;
1071
- if (ai !== bi) return ai - bi;
1072
- return a.planId.localeCompare(b.planId);
693
+
694
+ // src/client/i18n/format.ts
695
+ function formatMessage(template, params) {
696
+ return template.replace(/\{(\w+)\}/g, (match, name) => {
697
+ const value = params[name];
698
+ return value === void 0 ? match : String(value);
1073
699
  });
1074
700
  }
1075
- function buildSnapshots(data, options = {}) {
1076
- const locale = options.locale ?? DEFAULT_SA_LOCALE;
1077
- const sortPlansFn = sortPlansBy(options.planSortOrder);
1078
- const drafts = buildDraftsSnapshot(data, sortPlansFn, locale);
1079
- const active = buildActiveSnapshot(data, sortPlansFn, locale);
1080
- const historical = buildHistoricalSnapshots(data, sortPlansFn, locale);
1081
- const out = [drafts];
1082
- if (active) out.push(active);
1083
- out.push(...historical);
1084
- return out;
1085
- }
1086
- function listOpenDrafts(data) {
1087
- return {
1088
- plans: sortByPublishedDesc(data.planVersions).filter((r) => r.publishedAt === null)
1089
- };
1090
- }
1091
701
 
1092
702
  // src/client/i18n/currency.ts
1093
703
  var DEFAULT_CURRENCY = "EUR";
@@ -2955,6 +2565,114 @@ var planEditorMessages = defineMessages(
2955
2565
  }
2956
2566
  );
2957
2567
 
2568
+ // src/client/i18n/messages/plan-versions.ts
2569
+ var planVersionsMessages = defineMessages(
2570
+ {
2571
+ compareEnd: "Vergleich beenden",
2572
+ status: {
2573
+ draft: "DRAFT",
2574
+ active: "AKTIV",
2575
+ archived: "ARCHIV"
2576
+ },
2577
+ timeline: {
2578
+ title: "Plan-Catalog",
2579
+ snapshotCount: "{count} Snapshots",
2580
+ noOpenDrafts: "keine offenen Drafts",
2581
+ openCount: "{count} offen",
2582
+ unknown: "unbekannt",
2583
+ compareHint: "Rechtsklick auf Snapshot \u2192 vergleichen"
2584
+ },
2585
+ diff: {
2586
+ legend: "Vergleich",
2587
+ plansChanged: "Pakete ge\xE4ndert",
2588
+ regressions: "Regressionen",
2589
+ sectionPlans: "Pakete",
2590
+ changeCountOne: "{count} \xC4nderung",
2591
+ changeCountMany: "{count} \xC4nderungen"
2592
+ },
2593
+ diffPreview: {
2594
+ empty: "Keine \xC4nderungen gegen\xFCber der Vorg\xE4ngerversion.",
2595
+ improvement: "Verbesserung",
2596
+ regression: "Verschlechterung",
2597
+ neutral: "Neutral"
2598
+ },
2599
+ diffFields: {
2600
+ featuresAdded: "Hinzugef\xFCgte Features",
2601
+ featuresRemoved: "Entfernte Features",
2602
+ maxUsers: "Max. Benutzer",
2603
+ maxStorageGb: "Speicher (GB)",
2604
+ monthlyNet: "Preis monatlich (netto)",
2605
+ yearlyNet: "Preis j\xE4hrlich (netto)",
2606
+ unitSize: "Einheitsgr\xF6\xDFe"
2607
+ },
2608
+ bulkPublish: {
2609
+ changeNoteRequired: "changeNote ist Pflicht beim Publish."
2610
+ },
2611
+ format: {
2612
+ today: "heute",
2613
+ yesterday: "gestern",
2614
+ daysAgo: "vor {count} Tagen",
2615
+ weeksAgoOne: "vor {count} Woche",
2616
+ weeksAgoMany: "vor {count} Wochen",
2617
+ monthsAgo: "vor {count} Monaten",
2618
+ yearsAgoOne: "vor {count} Jahr",
2619
+ yearsAgoMany: "vor {count} Jahren"
2620
+ }
2621
+ },
2622
+ {
2623
+ compareEnd: "End comparison",
2624
+ status: {
2625
+ draft: "DRAFT",
2626
+ active: "ACTIVE",
2627
+ archived: "ARCHIVED"
2628
+ },
2629
+ timeline: {
2630
+ title: "Plan catalog",
2631
+ snapshotCount: "{count} snapshots",
2632
+ noOpenDrafts: "no open drafts",
2633
+ openCount: "{count} open",
2634
+ unknown: "unknown",
2635
+ compareHint: "Right-click a snapshot \u2192 compare"
2636
+ },
2637
+ diff: {
2638
+ legend: "Comparison",
2639
+ plansChanged: "Plans changed",
2640
+ regressions: "Regressions",
2641
+ sectionPlans: "Plans",
2642
+ changeCountOne: "{count} change",
2643
+ changeCountMany: "{count} changes"
2644
+ },
2645
+ diffPreview: {
2646
+ empty: "No changes compared to the previous version.",
2647
+ improvement: "Improvement",
2648
+ regression: "Regression",
2649
+ neutral: "Neutral"
2650
+ },
2651
+ diffFields: {
2652
+ featuresAdded: "Added features",
2653
+ featuresRemoved: "Removed features",
2654
+ maxUsers: "Max. users",
2655
+ maxStorageGb: "Storage (GB)",
2656
+ monthlyNet: "Price monthly (net)",
2657
+ yearlyNet: "Price yearly (net)",
2658
+ unitSize: "Unit size"
2659
+ },
2660
+ bulkPublish: {
2661
+ changeNoteRequired: "changeNote is required when publishing."
2662
+ },
2663
+ format: {
2664
+ today: "today",
2665
+ yesterday: "yesterday",
2666
+ daysAgo: "{count} days ago",
2667
+ weeksAgoOne: "{count} week ago",
2668
+ weeksAgoMany: "{count} weeks ago",
2669
+ monthsAgo: "{count} months ago",
2670
+ yearsAgoOne: "{count} year ago",
2671
+ yearsAgoMany: "{count} years ago"
2672
+ }
2673
+ }
2674
+ );
2675
+
2958
2676
  // src/client/i18n/messages/plans.ts
2959
2677
  var plansMessages = defineMessages(
2960
2678
  {
@@ -3971,7 +3689,7 @@ function resolveMessages(locale, overrides) {
3971
3689
  SA_MESSAGES,
3972
3690
  buildRoutes,
3973
3691
  buildSidebar,
3974
- buildSnapshots,
3692
+ createAdminResourceClient,
3975
3693
  defaultHttpClient,
3976
3694
  defaultKvStore,
3977
3695
  defaultSectionOrder,
@@ -3979,7 +3697,6 @@ function resolveMessages(locale, overrides) {
3979
3697
  formatCurrency,
3980
3698
  formatMessage,
3981
3699
  getJson,
3982
- listOpenDrafts,
3983
3700
  mergeMessages,
3984
3701
  postJson,
3985
3702
  resolveExtension,