@visns-studio/visns-components 6.17.2 → 6.20.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 (83) hide show
  1. package/package.json +10 -7
  2. package/src/components/AssociationManager.jsx +344 -177
  3. package/src/components/AsyncSelect.jsx +2 -2
  4. package/src/components/Autocomplete.jsx +57 -12
  5. package/src/components/Breadcrumb.jsx +23 -4
  6. package/src/components/ClientAssociationManager.jsx +321 -145
  7. package/src/components/DataGrid.jsx +625 -20
  8. package/src/components/DropZone.jsx +4 -1
  9. package/src/components/Form.jsx +233 -6
  10. package/src/components/MultiSelect.jsx +2 -2
  11. package/src/components/Navigation.jsx +39 -23
  12. package/src/components/TableFilter.jsx +172 -12
  13. package/src/components/UniversalSearch.jsx +390 -0
  14. package/src/components/auth/Login.jsx +164 -8
  15. package/src/components/auth/Profile.jsx +209 -22
  16. package/src/components/auth/authEndpoints.js +4 -0
  17. package/src/components/auth/webauthn.js +194 -0
  18. package/src/components/columns/ColumnRenderers.jsx +6 -1
  19. package/src/components/controls/DataGridSearch.jsx +11 -5
  20. package/src/components/generic/GenericAuth.jsx +6 -0
  21. package/src/components/generic/GenericDashboard.jsx +93 -36
  22. package/src/components/generic/GenericDetail.jsx +146 -61
  23. package/src/components/generic/GenericIndex.jsx +134 -38
  24. package/src/components/generic/GenericSettings.jsx +220 -0
  25. package/src/components/generic/StandardModal.jsx +165 -8
  26. package/src/components/generic/dashboardChartDefaults.js +230 -0
  27. package/src/components/generic/shared/indexLayout.js +38 -0
  28. package/src/components/settings/IntegrationsManager.jsx +474 -0
  29. package/src/components/sms/SmsLineSettings.jsx +16 -12
  30. package/src/components/styles/AssociationManager.module.scss +4 -363
  31. package/src/components/styles/AuditLog.module.scss +6 -6
  32. package/src/components/styles/AuditLogs.module.scss +3 -3
  33. package/src/components/styles/Autocomplete.module.scss +7 -7
  34. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  35. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  36. package/src/components/styles/CameraPlacement.module.scss +19 -19
  37. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  38. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  39. package/src/components/styles/ClientAuth.module.scss +1 -1
  40. package/src/components/styles/DataGrid.module.scss +455 -8
  41. package/src/components/styles/Field.module.scss +32 -24
  42. package/src/components/styles/Form.module.scss +123 -3
  43. package/src/components/styles/GenericDashboard.module.scss +612 -825
  44. package/src/components/styles/GenericDetail.module.scss +406 -61
  45. package/src/components/styles/GenericDynamic.module.scss +25 -25
  46. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  47. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  48. package/src/components/styles/GenericIndex.module.scss +483 -38
  49. package/src/components/styles/GenericMain.module.scss +6 -3
  50. package/src/components/styles/GenericQuote.module.scss +399 -81
  51. package/src/components/styles/GenericReport.module.scss +8 -8
  52. package/src/components/styles/GenericSettings.module.scss +209 -0
  53. package/src/components/styles/GenericSort.module.scss +9 -9
  54. package/src/components/styles/Integrations.module.scss +321 -0
  55. package/src/components/styles/List.module.scss +4 -4
  56. package/src/components/styles/Login.module.scss +57 -0
  57. package/src/components/styles/MultiSelect.module.scss +4 -4
  58. package/src/components/styles/Navigation.module.scss +12 -12
  59. package/src/components/styles/Notification.module.scss +12 -8
  60. package/src/components/styles/NotificationList.module.scss +5 -5
  61. package/src/components/styles/Profile.module.scss +5 -5
  62. package/src/components/styles/QrCode.module.scss +4 -4
  63. package/src/components/styles/QuickAction.module.scss +20 -20
  64. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  65. package/src/components/styles/TableFilter.module.scss +128 -17
  66. package/src/components/styles/TicketConversation.module.scss +497 -0
  67. package/src/components/styles/UniversalSearch.module.scss +229 -0
  68. package/src/components/styles/Vault.module.scss +413 -0
  69. package/src/components/styles/_associationManager.scss +467 -0
  70. package/src/components/styles/_authBrandPanel.scss +15 -9
  71. package/src/components/styles/_controls.scss +43 -39
  72. package/src/components/styles/global-datagrid.css +196 -0
  73. package/src/components/styles/global.css +168 -17
  74. package/src/components/tickets/TicketConversation.jsx +455 -0
  75. package/src/components/utils/rowActionSettings.js +117 -0
  76. package/src/components/vault/VaultClientPicker.jsx +183 -0
  77. package/src/components/vault/VaultEntryForm.jsx +23 -0
  78. package/src/components/vault/VaultManager.jsx +382 -34
  79. package/src/components/vault/VaultShareModal.jsx +454 -0
  80. package/src/components/vault/vaultEndpoints.js +16 -2
  81. package/src/components/vault/vaultShares.js +247 -0
  82. package/src/index.js +35 -0
  83. package/src/utils/fetchUtil.js +38 -14
@@ -36,7 +36,12 @@ const VisnsAutocomplete = memo((props) => {
36
36
  (value) => {
37
37
  if (!value || !api) return null;
38
38
 
39
- let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json?types=address,poi&access_token=${api}`;
39
+ // The search text is a PATH segment, so it has to be escaped: an
40
+ // unencoded "5/12 Smith St" split the path and 404'd, which is a
41
+ // shape half of Australian addresses take.
42
+ let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
43
+ value
44
+ )}.json?types=address,poi&access_token=${api}`;
40
45
 
41
46
  if (country) {
42
47
  url += `&country=${country}`;
@@ -149,20 +154,39 @@ const VisnsAutocomplete = memo((props) => {
149
154
 
150
155
  const finalValue = displayValue || fallbackValue;
151
156
 
152
- setSearch(finalValue);
153
157
  setResults([]);
154
158
  setIsFocused(false);
155
159
 
156
- // Update form data with the selected value
160
+ /*
161
+ * A form that handles the pick owns this field's value.
162
+ *
163
+ * It splits the place across its own fields — street line,
164
+ * suburb, postcode, state — and only the street line belongs in
165
+ * this input. Writing Mapbox's `place_name` here first left the
166
+ * box showing the whole formatted address, suburb and state
167
+ * included, right next to the fields that had just been filled
168
+ * with those same parts: the field looked like it had mangled
169
+ * the address rather than parsed it.
170
+ *
171
+ * The `inputValue` effect below cannot undo that, because it
172
+ * only fires when the form's value *changes*, and the form's
173
+ * street line is usually exactly what the user typed. So don't
174
+ * write the wrong value in the first place: hand the place to
175
+ * the form and follow whatever it puts back.
176
+ */
177
+ if (onSelect) {
178
+ onSelect(place, field);
179
+ return;
180
+ }
181
+
182
+ setSearch(finalValue);
183
+
184
+ // Standalone use (no form to parse the place): the field keeps
185
+ // the full formatted address, which is the only useful value.
157
186
  setFormData((prevState) => ({
158
187
  ...prevState,
159
188
  [field]: finalValue,
160
189
  }));
161
-
162
- // Call the onSelect callback with the place object and field
163
- if (onSelect) {
164
- onSelect(place, field);
165
- }
166
190
  },
167
191
  [field, onSelect, setFormData]
168
192
  );
@@ -176,13 +200,34 @@ const VisnsAutocomplete = memo((props) => {
176
200
  });
177
201
  }, []);
178
202
 
179
- // Effect to handle overwrite toggle
203
+ // What is in the box, readable without making it a trigger.
204
+ const searchRef = useRef('');
205
+ const isFocusedRef = useRef(false);
206
+ searchRef.current = search;
207
+ isFocusedRef.current = isFocused;
208
+
209
+ /*
210
+ * Switching autocomplete back on searches for whatever is already typed.
211
+ *
212
+ * Deliberately NOT keyed on `search`: typing is already handled by the
213
+ * debounced path, and this effect calls `performSearch` directly. Keyed
214
+ * on `search` it fired once per keystroke and bypassed the 500ms debounce
215
+ * entirely — 21 characters produced 21 Mapbox requests (42 with the CORS
216
+ * preflight each one triggered), where the debounce is there to produce
217
+ * one. Focus is handled by `handleFocus`, so it is not a trigger either.
218
+ */
180
219
  useEffect(() => {
181
- if (!overwrite && search && search.length > 1 && isFocused) {
220
+ if (overwrite) {
221
+ return;
222
+ }
223
+
224
+ const value = searchRef.current;
225
+
226
+ if (isFocusedRef.current && value && value.length > 1) {
182
227
  setIsLoading(true);
183
- performSearch(1, search);
228
+ performSearch(1, value);
184
229
  }
185
- }, [overwrite, search, performSearch, isFocused]);
230
+ }, [overwrite, performSearch]);
186
231
 
187
232
  // Effect to sync with inputValue prop
188
233
  useEffect(() => {
@@ -4,6 +4,21 @@ import dayjs from 'dayjs';
4
4
  import parse from 'html-react-parser';
5
5
  import { ChevronRight } from 'lucide-react';
6
6
 
7
+ /**
8
+ * The page header trail: parent link → page → record name.
9
+ *
10
+ * It stays an `<h1>` on purpose. Three stylesheets — GenericIndex,
11
+ * GenericDetail and GenericFormBuilder — style this element through
12
+ * `.crmtitle h1` descendant selectors, so changing the tag to the `<nav><ol>`
13
+ * a breadcrumb "should" be would silently restyle every index, detail and
14
+ * form-builder page in the app. The semantics are fixed the way that costs
15
+ * nothing instead: a labelled `nav` around it, the separators hidden from
16
+ * assistive tech, and the final crumb marked as the current page.
17
+ *
18
+ * Before this, a screen reader read the whole trail as one heading —
19
+ * "Customers greater-than Acme Pty Ltd" — with the chevrons announced as
20
+ * content and no indication which part was where you actually were.
21
+ */
7
22
  function Breadcrumb({ data, page }) {
8
23
  // Function to get nested data, handling array of keys
9
24
  const getNestedData = (data, keys) => {
@@ -69,6 +84,7 @@ function Breadcrumb({ data, page }) {
69
84
  };
70
85
 
71
86
  return (
87
+ <nav aria-label="Breadcrumb">
72
88
  <h1>
73
89
  {page?.parentTitle && (
74
90
  <>
@@ -77,7 +93,7 @@ function Breadcrumb({ data, page }) {
77
93
  ) : (
78
94
  <span>{parse(page.parentTitle)}</span>
79
95
  )}
80
- <ChevronRight strokeWidth={2} size={18} />
96
+ <ChevronRight strokeWidth={2} size={16} aria-hidden="true" />
81
97
  </>
82
98
  )}
83
99
 
@@ -113,9 +129,11 @@ function Breadcrumb({ data, page }) {
113
129
  )
114
130
  )}
115
131
 
132
+ {/* The record name is the last crumb whenever there is one, so it
133
+ is the one that carries "you are here". */}
116
134
  {page.titleKey && data && (
117
- <>
118
- <ChevronRight strokeWidth={2} size={18} />
135
+ <span aria-current="page">
136
+ <ChevronRight strokeWidth={2} size={16} aria-hidden="true" />
119
137
  {Array.isArray(page.titleKey) ? (
120
138
  <>
121
139
  {page.titleText && (
@@ -145,7 +163,7 @@ function Breadcrumb({ data, page }) {
145
163
  )}
146
164
  </>
147
165
  )}
148
- </>
166
+ </span>
149
167
  )}
150
168
 
151
169
  {/* Display image if condition is met */}
@@ -163,6 +181,7 @@ function Breadcrumb({ data, page }) {
163
181
  />
164
182
  )}
165
183
  </h1>
184
+ </nav>
166
185
  );
167
186
  }
168
187