@necrolab/dashboard 0.5.15 → 0.5.16

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 (121) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +140 -170
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +58 -15
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +12 -20
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +220 -0
  20. package/src/assets/css/main.scss +66 -77
  21. package/src/components/Auth/LoginForm.vue +5 -84
  22. package/src/components/Editors/Account/Account.vue +8 -10
  23. package/src/components/Editors/Account/AccountCreator.vue +28 -59
  24. package/src/components/Editors/Account/AccountView.vue +38 -86
  25. package/src/components/Editors/Account/CreateAccount.vue +8 -50
  26. package/src/components/Editors/Profile/CreateProfile.vue +74 -131
  27. package/src/components/Editors/Profile/Profile.vue +15 -17
  28. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  29. package/src/components/Editors/Profile/ProfileView.vue +46 -96
  30. package/src/components/Editors/TagLabel.vue +16 -55
  31. package/src/components/Editors/TagToggle.vue +20 -8
  32. package/src/components/Filter/Filter.vue +62 -75
  33. package/src/components/Filter/FilterPreview.vue +161 -135
  34. package/src/components/Filter/PriceSortToggle.vue +36 -43
  35. package/src/components/Table/Header.vue +1 -1
  36. package/src/components/Table/Table.vue +45 -51
  37. package/src/components/Tasks/CheckStock.vue +7 -16
  38. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  39. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  40. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  41. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  42. package/src/components/Tasks/EventDetailRow.vue +21 -0
  43. package/src/components/Tasks/MassEdit.vue +6 -16
  44. package/src/components/Tasks/QuickSettings.vue +140 -216
  45. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  46. package/src/components/Tasks/Stats.vue +19 -38
  47. package/src/components/Tasks/Task.vue +65 -268
  48. package/src/components/Tasks/TaskLabel.vue +9 -3
  49. package/src/components/Tasks/TaskView.vue +43 -63
  50. package/src/components/Tasks/Utilities.vue +10 -44
  51. package/src/components/Tasks/ViewTask.vue +23 -107
  52. package/src/components/icons/Close.vue +2 -8
  53. package/src/components/icons/Gear.vue +8 -8
  54. package/src/components/icons/Hash.vue +5 -0
  55. package/src/components/icons/Key.vue +2 -8
  56. package/src/components/icons/Pencil.vue +2 -8
  57. package/src/components/icons/Profile.vue +2 -8
  58. package/src/components/icons/Sell.vue +2 -8
  59. package/src/components/icons/Spinner.vue +4 -7
  60. package/src/components/icons/SquareCheck.vue +2 -8
  61. package/src/components/icons/SquareUncheck.vue +2 -8
  62. package/src/components/icons/Wildcard.vue +2 -8
  63. package/src/components/icons/index.js +3 -1
  64. package/src/components/ui/ActionButtonGroup.vue +113 -52
  65. package/src/components/ui/BalanceIndicator.vue +60 -0
  66. package/src/components/ui/EmptyState.vue +24 -0
  67. package/src/components/ui/EnableDisableToggle.vue +23 -0
  68. package/src/components/ui/FormField.vue +48 -48
  69. package/src/components/ui/IconLabel.vue +23 -0
  70. package/src/components/ui/InfoRow.vue +21 -54
  71. package/src/components/ui/Modal.vue +89 -56
  72. package/src/components/ui/Navbar.vue +60 -41
  73. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  74. package/src/components/ui/ReconnectIndicator.vue +111 -124
  75. package/src/components/ui/SectionCard.vue +6 -14
  76. package/src/components/ui/Splash.vue +2 -10
  77. package/src/components/ui/StatusBadge.vue +26 -28
  78. package/src/components/ui/TaskToggle.vue +54 -0
  79. package/src/components/ui/controls/CountryChooser.vue +27 -64
  80. package/src/components/ui/controls/EyeToggle.vue +1 -1
  81. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  82. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  83. package/src/components/ui/controls/atomic/MultiDropdown.vue +71 -119
  84. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  85. package/src/composables/useColorMapping.js +15 -0
  86. package/src/composables/useCopyToClipboard.js +1 -1
  87. package/src/composables/useDateFormatting.js +21 -0
  88. package/src/composables/useDeviceDetection.js +14 -0
  89. package/src/composables/useDropdownPosition.js +3 -4
  90. package/src/composables/useDynamicTableHeight.js +31 -0
  91. package/src/composables/useRowSelection.js +0 -3
  92. package/src/composables/useTicketPricing.js +16 -0
  93. package/src/composables/useWindowDimensions.js +21 -0
  94. package/src/libs/Filter.js +14 -20
  95. package/src/libs/panzoom.js +1 -5
  96. package/src/libs/utils/array.js +60 -0
  97. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  98. package/src/libs/utils/eventUrl.js +40 -0
  99. package/src/libs/utils/string.js +28 -0
  100. package/src/libs/utils/time.js +20 -0
  101. package/src/libs/utils/validation.js +88 -0
  102. package/src/main.js +0 -2
  103. package/src/stores/connection.js +1 -4
  104. package/src/stores/logger.js +6 -12
  105. package/src/stores/sampleData.js +1 -2
  106. package/src/stores/ui.js +59 -36
  107. package/src/views/Accounts.vue +13 -24
  108. package/src/views/Console.vue +70 -172
  109. package/src/views/Editor.vue +211 -379
  110. package/src/views/FilterBuilder.vue +188 -371
  111. package/src/views/Login.vue +3 -28
  112. package/src/views/Profiles.vue +8 -15
  113. package/src/views/Tasks.vue +49 -36
  114. package/tailwind.config.js +82 -71
  115. package/workbox-config.cjs +47 -5
  116. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  117. package/exit +0 -209
  118. package/run +0 -177
  119. package/src/assets/css/base/color-fallbacks.scss +0 -10
  120. package/switch-branch.sh +0 -41
  121. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,96 +1,33 @@
1
1
  <template>
2
- <label class="switch" :class="{ 'disabled': disabled }">
3
- <input type="checkbox" v-model="value" :disabled="disabled" />
4
- <span class="slider round"></span>
2
+ <label
3
+ class="relative inline-block w-[51px] h-[31px] max-modal:w-11 max-modal:min-w-11 max-modal:h-6.5"
4
+ :class="disabled ? 'pointer-events-none' : ''"
5
+ >
6
+ <input
7
+ type="checkbox"
8
+ v-model="value"
9
+ :disabled="disabled"
10
+ class="opacity-0 w-0 h-0 peer"
11
+ role="switch"
12
+ :aria-checked="value"
13
+ />
14
+ <span
15
+ class="absolute inset-0 cursor-pointer border-2 rounded-[31px] transition-all duration-300 ease-out
16
+ before:absolute before:content-[''] before:size-[23px] before:left-0.5 before:bottom-0.5 before:rounded-full before:shadow-switch before:transition-all before:duration-300 before:ease-out
17
+ bg-dark-550 border-dark-550 before:bg-dark-200
18
+ peer-checked:bg-accent-green peer-checked:border-accent-green peer-checked:before:bg-white peer-checked:before:translate-x-5
19
+ peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:before:opacity-70
20
+ max-modal:before:size-[22px] max-modal:peer-checked:before:translate-x-[18px]"
21
+ ></span>
5
22
  </label>
6
23
  </template>
7
24
 
8
25
  <script setup>
9
26
  const value = defineModel();
10
- const props = defineProps({
27
+ defineProps({
11
28
  disabled: {
12
29
  type: Boolean,
13
30
  default: false
14
31
  }
15
32
  });
16
33
  </script>
17
-
18
- <style lang="scss" scoped>
19
- /* iOS-style switch */
20
- .switch {
21
- @apply relative inline-block;
22
- width: 51px;
23
- height: 31px;
24
- }
25
-
26
- /* Hide default HTML checkbox */
27
- .switch input {
28
- @apply opacity-0 w-0 h-0;
29
- }
30
-
31
- /* The slider */
32
- .slider {
33
- @apply absolute cursor-pointer inset-0;
34
- @apply border-2;
35
- background-color: oklch(0.26 0 0);
36
- border-color: oklch(0.35 0 0);
37
- transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
38
- }
39
-
40
- .slider:before {
41
- @apply absolute;
42
- @apply w-[23px] h-[23px] left-0.5 bottom-0.5;
43
- content: "";
44
- background-color: oklch(0.50 0 0);
45
- transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
46
- box-shadow: 0 1px 3px oklch(0 0 0 / 0.3);
47
- }
48
-
49
- input:checked + .slider {
50
- background-color: oklch(0.72 0.15 145);
51
- border-color: oklch(0.72 0.15 145);
52
- }
53
-
54
- input:checked + .slider:before {
55
- background-color: oklch(1 0 0);
56
- }
57
-
58
- input:checked + .slider:before {
59
- transform: translateX(20px);
60
- }
61
-
62
- .switch.disabled {
63
- @apply pointer-events-none;
64
- }
65
-
66
- input:disabled + .slider {
67
- @apply opacity-50 cursor-not-allowed;
68
- }
69
-
70
- input:disabled + .slider:before {
71
- @apply opacity-70;
72
- }
73
-
74
- /* Rounded sliders */
75
- .slider.round {
76
- border-radius: 31px;
77
- }
78
-
79
- .slider.round:before {
80
- border-radius: 50%;
81
- }
82
-
83
- @media (max-width: 810px) {
84
- .switch {
85
- @apply w-11 min-w-11 h-[26px];
86
-
87
- .slider:before {
88
- @apply w-[22px] h-[22px] left-0.5 bottom-0.5;
89
- }
90
- }
91
-
92
- input:checked + .slider:before {
93
- @apply translate-x-[18px];
94
- }
95
- }
96
- </style>
@@ -0,0 +1,15 @@
1
+ export function useColorMapping() {
2
+ const colorToClass = (color) => {
3
+ const colorMap = {
4
+ green: "bg-green-400",
5
+ red: "bg-red-400",
6
+ yellow: "bg-yellow-400",
7
+ blue: "bg-blue-400",
8
+ error: "bg-red-400",
9
+ success: "bg-green-400"
10
+ };
11
+ return colorMap[color?.toLowerCase()] || "bg-white";
12
+ };
13
+
14
+ return { colorToClass };
15
+ }
@@ -17,7 +17,7 @@ export function useCopyToClipboard() {
17
17
  })
18
18
  .catch((err) => {
19
19
  ui.showError('Failed to copy')
20
- console.error('Copy failed:', err)
20
+ ui.logger.Error('Copy failed:', err)
21
21
  })
22
22
  }
23
23
 
@@ -0,0 +1,21 @@
1
+ export function useDateFormatting() {
2
+ const formatEventDate = (dateString) => {
3
+ if (!dateString) return '';
4
+ try {
5
+ const date = new Date(dateString);
6
+ const options = {
7
+ month: 'short',
8
+ day: 'numeric',
9
+ year: 'numeric',
10
+ hour: 'numeric',
11
+ minute: '2-digit',
12
+ hour12: true
13
+ };
14
+ return date.toLocaleString('en-US', options).replace(',', '');
15
+ } catch {
16
+ return dateString;
17
+ }
18
+ };
19
+
20
+ return { formatEventDate };
21
+ }
@@ -0,0 +1,14 @@
1
+ export function useDeviceDetection() {
2
+ const isIOS = () => {
3
+ if (/iPad|iPhone|iPod/.test(navigator.platform)) {
4
+ return true;
5
+ }
6
+ return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
7
+ };
8
+
9
+ const isIpadOS = () => {
10
+ return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
11
+ };
12
+
13
+ return { isIOS, isIpadOS };
14
+ }
@@ -1,4 +1,5 @@
1
1
  import { ref, nextTick, onMounted, onUnmounted } from "vue";
2
+ import { DEBUG } from "@/utils/debug";
2
3
 
3
4
  export function useDropdownPosition(dropdownRef, options = {}) {
4
5
  const menuStyle = ref({});
@@ -8,8 +9,7 @@ export function useDropdownPosition(dropdownRef, options = {}) {
8
9
  minWidth = null,
9
10
  maxHeight = window.innerHeight * 0.8, // Use 80% of viewport height
10
11
  estimateHeight = null,
11
- includeAdjacentButtons = false,
12
- containerSelector = null
12
+ includeAdjacentButtons = false
13
13
  } = options;
14
14
 
15
15
  const calculateMenuPosition = () => {
@@ -109,8 +109,7 @@ export function useDropdownPosition(dropdownRef, options = {}) {
109
109
  maxHeight: `${maxHeight}px`
110
110
  };
111
111
  } catch (error) {
112
- console.warn("Error calculating dropdown position:", error);
113
- // Fallback to basic positioning
112
+ if (DEBUG) console.warn("Error calculating dropdown position:", error);
114
113
  menuStyle.value = {
115
114
  position: "fixed",
116
115
  top: "0px",
@@ -0,0 +1,31 @@
1
+ import { computed } from "vue";
2
+ import { useWindowDimensions } from "./useWindowDimensions";
3
+
4
+ export function useDynamicTableHeight(options = {}) {
5
+ const { windowHeight, windowWidth } = useWindowDimensions();
6
+
7
+ const {
8
+ topReservedSpace = 243,
9
+ bottomBuffer = 16,
10
+ rowHeight = 64,
11
+ minRowsToShow = 2
12
+ } = options;
13
+
14
+ const dynamicTableHeight = computed(() => {
15
+ // Detect PWA mode and small screens
16
+ const isPWA = window.matchMedia('(display-mode: standalone)').matches;
17
+ const isMobile = windowWidth.value <= 768;
18
+
19
+ // Extra buffer for iPhone PWA to prevent overflow
20
+ const extraBuffer = isPWA && isMobile ? 60 : 0;
21
+
22
+ const availableHeight = windowHeight.value - topReservedSpace - bottomBuffer - extraBuffer;
23
+ const minHeight = minRowsToShow * rowHeight;
24
+ const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
25
+ const exactHeight = maxCompleteRows * rowHeight;
26
+
27
+ return exactHeight + "px";
28
+ });
29
+
30
+ return { dynamicTableHeight };
31
+ }
@@ -12,7 +12,6 @@ export function useRowSelection(toggleCallback) {
12
12
  const DOUBLE_TAP_DELAY = 300
13
13
 
14
14
  const handleDoubleClick = (event) => {
15
- // Don't trigger on button or checkbox clicks
16
15
  if (event.target.closest('button') || event.target.closest('.checkbox')) {
17
16
  return
18
17
  }
@@ -24,7 +23,6 @@ export function useRowSelection(toggleCallback) {
24
23
  const tapGap = currentTime - lastTapTime
25
24
 
26
25
  if (tapGap < DOUBLE_TAP_DELAY && tapGap > 0) {
27
- // Don't trigger on button or checkbox taps
28
26
  if (!event.target.closest('button') && !event.target.closest('.checkbox')) {
29
27
  event.preventDefault()
30
28
  toggleCallback()
@@ -34,7 +32,6 @@ export function useRowSelection(toggleCallback) {
34
32
  }
35
33
 
36
34
  const handleTouchEnd = (event) => {
37
- // Prevent default touch behavior on buttons/checkboxes
38
35
  if (event.target.closest('button') || event.target.closest('.checkbox')) {
39
36
  return
40
37
  }
@@ -0,0 +1,16 @@
1
+ export function useTicketPricing() {
2
+ const isTotalPrice = (line, index, lines) => {
3
+ const trimmed = line.trim();
4
+ if (!trimmed) return false;
5
+
6
+ const nonEmptyLines = lines.filter(l => l.trim());
7
+ const isLastLine = index === lines.lastIndexOf(nonEmptyLines[nonEmptyLines.length - 1]);
8
+
9
+ if (!isLastLine) return false;
10
+
11
+ const totalPricePattern = /^([$€£¥₹₽¢]|[A-Z]{3})\s*[\d,]+\.?\d*$/;
12
+ return totalPricePattern.test(trimmed) && !trimmed.includes('(') && !trimmed.includes(')');
13
+ };
14
+
15
+ return { isTotalPrice };
16
+ }
@@ -0,0 +1,21 @@
1
+ import { ref, onMounted, onUnmounted } from "vue";
2
+
3
+ export function useWindowDimensions() {
4
+ const windowHeight = ref(window.innerHeight);
5
+ const windowWidth = ref(window.innerWidth);
6
+
7
+ const updateDimensions = () => {
8
+ windowHeight.value = window.innerHeight;
9
+ windowWidth.value = window.innerWidth;
10
+ };
11
+
12
+ onMounted(() => {
13
+ window.addEventListener("resize", updateDimensions);
14
+ });
15
+
16
+ onUnmounted(() => {
17
+ window.removeEventListener("resize", updateDimensions);
18
+ });
19
+
20
+ return { windowHeight, windowWidth };
21
+ }
@@ -1,5 +1,5 @@
1
- const debug = false;
2
- const log = (...args) => debug && console.log("[filter]", ...args);
1
+ import { DEBUG } from "@/utils/debug";
2
+ const log = (...args) => DEBUG && console.log("[filter]", ...args);
3
3
 
4
4
  const colors = {
5
5
  HIGHLIGHT: "#d3f8e2",
@@ -30,27 +30,23 @@ const isWheelchair = (p) => {
30
30
  };
31
31
 
32
32
  const sortAlphaNum = (a, b) => {
33
- var reA = /[^a-zA-Z]/g;
34
- var reN = /[^0-9]/g;
35
- var AInt = parseInt(a, 10);
36
- var BInt = parseInt(b, 10);
33
+ const reA = /[^a-zA-Z]/g;
34
+ const reN = /[^0-9]/g;
35
+ const AInt = parseInt(a, 10);
36
+ const BInt = parseInt(b, 10);
37
37
 
38
38
  if (isNaN(AInt) && isNaN(BInt)) {
39
- var aA = a.replace(reA, "");
40
- var bA = b.replace(reA, "");
39
+ const aA = a.replace(reA, "");
40
+ const bA = b.replace(reA, "");
41
41
  if (aA === bA) {
42
- var aN = parseInt(a.replace(reN, ""), 10);
43
- var bN = parseInt(b.replace(reN, ""), 10);
42
+ const aN = parseInt(a.replace(reN, ""), 10);
43
+ const bN = parseInt(b.replace(reN, ""), 10);
44
44
  return aN === bN ? 0 : aN > bN ? 1 : -1;
45
45
  } else {
46
46
  return aA > bA ? 1 : -1;
47
47
  }
48
- } else if (isNaN(AInt)) {
49
- //A is not an Int
50
- return 1; //to make alphanumeric sort first return -1 here
51
48
  } else if (isNaN(BInt)) {
52
- //B is not an Int
53
- return -1; //to make alphanumeric sort first return 1 here
49
+ return -1;
54
50
  } else {
55
51
  return AInt > BInt ? 1 : -1;
56
52
  }
@@ -313,7 +309,7 @@ export default class FilterBuilder {
313
309
  });
314
310
 
315
311
  if (existingFilter) {
316
- console.log("Filter already exists:", existingFilter);
312
+ if (DEBUG) log("Filter already exists:", existingFilter);
317
313
  return; // Don't add the filter if it already exists
318
314
  }
319
315
 
@@ -385,7 +381,7 @@ export default class FilterBuilder {
385
381
  } else {
386
382
  color = this.expandedFilter === filter.id ? colors.SELECTED_EXPANDED : colors.SELECTED;
387
383
  }
388
-
384
+
389
385
  switch (type) {
390
386
  case this.filterTypes.NORMAL:
391
387
  // If it has no 'rows' property
@@ -416,7 +412,6 @@ export default class FilterBuilder {
416
412
  break;
417
413
 
418
414
  case this.filterTypes.CATCH_ALL_FLOOR:
419
- // this.cssClasses += floors.map((f) => `path[name="${f}"] {fill: ${color} !important;}`).join("\n") + "\n";
420
415
  break;
421
416
 
422
417
  case this.filterTypes.INVALID:
@@ -432,7 +427,7 @@ export default class FilterBuilder {
432
427
  const color = colors.UNSELECTABLE;
433
428
  this.cssClasses += `.svg-wrapper path[section="${section}"][row="${row}"] {stroke: ${color} !important;}\n`;
434
429
  });
435
-
430
+
436
431
  log("Generated CSS:", this.cssClasses);
437
432
  }
438
433
 
@@ -533,7 +528,6 @@ export default class FilterBuilder {
533
528
 
534
529
  deleteFilterById(id) {
535
530
  this.filters = this.filters.filter((f) => f.id !== id);
536
- // this.updateHooks = this.updateHooks.filter((i) => id !== i);
537
531
  if (this.expandedFilter === id) this.expandedFilter = "";
538
532
  this.updateCss();
539
533
  this.updateHooks.forEach((fn) => fn());
@@ -1,7 +1,6 @@
1
1
  export default (function (f) {
2
2
  const panzoom = f();
3
3
  window.panzoom = panzoom;
4
- // module.exports = panzoom;
5
4
  return panzoom;
6
5
  })(function () {
7
6
  var define, module, exports;
@@ -718,7 +717,6 @@ export default (function (f) {
718
717
  failTransformOrigin();
719
718
  }
720
719
  function failTransformOrigin(options) {
721
- console.error(options);
722
720
  throw new Error(
723
721
  [
724
722
  "Cannot parse transform origin.",
@@ -783,7 +781,6 @@ export default (function (f) {
783
781
  setTimeout(tryAttach, 100);
784
782
  return;
785
783
  }
786
- console.error("Cannot find the panzoom element", globalName);
787
784
  return;
788
785
  }
789
786
  var options = collectOptions(panzoomScript);
@@ -1107,8 +1104,7 @@ export default (function (f) {
1107
1104
  var easing = typeof options.easing === "function" ? options.easing : animations[options.easing];
1108
1105
  if (!easing) {
1109
1106
  if (options.easing) {
1110
- console.warn("Unknown easing function in amator: " + options.easing);
1111
- }
1107
+ }
1112
1108
  easing = animations.ease;
1113
1109
  }
1114
1110
  var step = typeof options.step === "function" ? options.step : noop;
@@ -0,0 +1,60 @@
1
+ const removeDuplicates = (arr) => [...new Set(arr)];
2
+
3
+ const pickRandom = (arr) => arr[Math.floor(Math.random() * arr.length)];
4
+
5
+ function betterSort(a, b) {
6
+ if (a === undefined || a === null) return b === undefined || b === null ? 0 : -1;
7
+ if (b === undefined || b === null) return 1;
8
+
9
+ const aStr = String(a);
10
+ const bStr = String(b);
11
+
12
+ if (!isNaN(aStr) && !isNaN(bStr)) {
13
+ return Number(aStr) - Number(bStr);
14
+ }
15
+
16
+ const aParts = aStr.split(/(\d+)/).filter(Boolean);
17
+ const bParts = bStr.split(/(\d+)/).filter(Boolean);
18
+
19
+ const len = Math.min(aParts.length, bParts.length);
20
+ for (let i = 0; i < len; i++) {
21
+ if (!isNaN(aParts[i]) && !isNaN(bParts[i])) {
22
+ const numA = parseInt(aParts[i], 10);
23
+ const numB = parseInt(bParts[i], 10);
24
+ if (numA !== numB) return numA - numB;
25
+ } else {
26
+ const cmp = aParts[i].localeCompare(bParts[i]);
27
+ if (cmp !== 0) return cmp;
28
+ }
29
+ }
30
+
31
+ return aParts.length - bParts.length;
32
+ }
33
+
34
+ function sortTaskIds(a, b) {
35
+ const parseId = (id) => {
36
+ if (!id) return { prefix: "", num: -1 };
37
+
38
+ if (/^\d+$/.test(id)) {
39
+ return { prefix: "", num: parseInt(id, 10) };
40
+ }
41
+
42
+ const match = id.match(/^([A-Za-z-]+)(\d+)$/);
43
+ if (match) {
44
+ return { prefix: match[1], num: parseInt(match[2], 10) };
45
+ }
46
+
47
+ return { prefix: id, num: -1 };
48
+ };
49
+
50
+ const aInfo = parseId(a);
51
+ const bInfo = parseId(b);
52
+
53
+ if (aInfo.prefix !== bInfo.prefix) {
54
+ return aInfo.prefix.localeCompare(bInfo.prefix);
55
+ }
56
+
57
+ return aInfo.num - bInfo.num;
58
+ }
59
+
60
+ export { removeDuplicates, pickRandom, betterSort, sortTaskIds };