@tellescope/react-components 1.183.0 → 1.185.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 (43) hide show
  1. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  2. package/lib/cjs/Forms/hooks.js +6 -5
  3. package/lib/cjs/Forms/hooks.js.map +1 -1
  4. package/lib/cjs/inputs_shared.d.ts +43 -0
  5. package/lib/cjs/inputs_shared.d.ts.map +1 -1
  6. package/lib/cjs/inputs_shared.js +433 -2
  7. package/lib/cjs/inputs_shared.js.map +1 -1
  8. package/lib/cjs/layout.d.ts.map +1 -1
  9. package/lib/cjs/layout.js +4 -2
  10. package/lib/cjs/layout.js.map +1 -1
  11. package/lib/cjs/state.d.ts +9 -2
  12. package/lib/cjs/state.d.ts.map +1 -1
  13. package/lib/cjs/state.js +117 -68
  14. package/lib/cjs/state.js.map +1 -1
  15. package/lib/cjs/table.d.ts +8 -3
  16. package/lib/cjs/table.d.ts.map +1 -1
  17. package/lib/cjs/table.js +17 -13
  18. package/lib/cjs/table.js.map +1 -1
  19. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  20. package/lib/esm/Forms/hooks.js +6 -5
  21. package/lib/esm/Forms/hooks.js.map +1 -1
  22. package/lib/esm/inputs_shared.d.ts +43 -0
  23. package/lib/esm/inputs_shared.d.ts.map +1 -1
  24. package/lib/esm/inputs_shared.js +427 -3
  25. package/lib/esm/inputs_shared.js.map +1 -1
  26. package/lib/esm/layout.d.ts.map +1 -1
  27. package/lib/esm/layout.js +4 -2
  28. package/lib/esm/layout.js.map +1 -1
  29. package/lib/esm/state.d.ts +9 -2
  30. package/lib/esm/state.d.ts.map +1 -1
  31. package/lib/esm/state.js +117 -68
  32. package/lib/esm/state.js.map +1 -1
  33. package/lib/esm/table.d.ts +8 -3
  34. package/lib/esm/table.d.ts.map +1 -1
  35. package/lib/esm/table.js +17 -13
  36. package/lib/esm/table.js.map +1 -1
  37. package/lib/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +9 -9
  39. package/src/Forms/hooks.tsx +3 -2
  40. package/src/inputs_shared.tsx +444 -5
  41. package/src/layout.tsx +10 -1
  42. package/src/state.tsx +89 -51
  43. package/src/table.tsx +32 -5
package/src/state.tsx CHANGED
@@ -165,7 +165,7 @@ export const WithFetchContext = ( { children } : { children: React.ReactNode })
165
165
  }
166
166
 
167
167
  // doesn't throw
168
- export const toLoadedData = async <T,>(p: () => Promise<T>): Promise<{
168
+ export const toLoadedData = async <T,>(p: () => Promise<T>, o?: { valueOnError?: T }): Promise<{
169
169
  status: LoadingStatus.Loaded, value: T,
170
170
  } | {
171
171
  status: LoadingStatus.Error, value: APIError
@@ -173,6 +173,7 @@ export const toLoadedData = async <T,>(p: () => Promise<T>): Promise<{
173
173
  try {
174
174
  return { status: LoadingStatus.Loaded, value: await p() }
175
175
  } catch(err: any) {
176
+ if (o?.valueOnError) { return { status: LoadingStatus.Loaded, value: o.valueOnError } }
176
177
  return { status: LoadingStatus.Error, value: err }
177
178
  }
178
179
  }
@@ -522,6 +523,7 @@ export interface LoadMoreOptions <T> {
522
523
  key?: string,
523
524
  limit?: number,
524
525
  filter?: ReadFilter<T> | undefined
526
+ mdbFilter?: Record<string, any>,
525
527
  }
526
528
 
527
529
  export interface LoadMoreFunctions<T> {
@@ -1030,25 +1032,28 @@ export const useListStateHook = <T extends { id: string | number }, ADD extends
1030
1032
  const sort = loadOptions?.sort ?? options?.sort
1031
1033
  const sortBy = loadOptions?.sortBy ?? options?.sortBy
1032
1034
 
1035
+ const _mdbFilter = loadOptions?.mdbFilter || options?.mdbFilter
1036
+ const mdbFilter = (_mdbFilter && _mdbFilter?.$and?.length) ? _mdbFilter : undefined
1037
+
1033
1038
  if (!loadQuery) return
1034
1039
  if (options?.dontFetch && !force) return
1035
- const fetchKey = (loadFilter || sort || sortBy) ? JSON.stringify({ ...loadFilter, sort, sortBy }) + modelName : modelName
1040
+ const fetchKey = (mdbFilter || loadFilter || sort || sortBy) ? JSON.stringify({ ...mdbFilter, ...loadFilter, sort, sortBy }) + modelName : modelName
1036
1041
 
1037
1042
  if (didFetch(fetchKey, force, options?.refetchInMS)) return
1038
1043
  setFetched(fetchKey, true)
1039
1044
 
1040
1045
  const limit = options?.limit || DEFAULT_FETCH_LIMIT
1041
- toLoadedData(() => loadQuery({ filter: loadFilter, limit, sort, sortBy })).then(
1046
+ toLoadedData(() => loadQuery({ mdbFilter, filter: loadFilter, limit, sort, sortBy }), { valueOnError: mdbFilter ? [] : undefined }).then(
1042
1047
  es => {
1043
1048
  if (es.status === LoadingStatus.Loaded) {
1044
- if (es.value.length < limit && !loadFilter) {
1049
+ if (es.value.length < limit && !loadFilter && !mdbFilter) {
1045
1050
  setFetched('id' + modelName + DONE_LOADING_TOKEN, true)
1046
1051
  }
1052
+ else if (es.value.length < limit) {
1053
+ setFetched(fetchKey + DONE_LOADING_TOKEN, true)
1054
+ }
1047
1055
  if (es.value.length) { // don't store oldest record from a filter, may skip some pages
1048
- setLastId(
1049
- modelName + (loadFilter ? JSON.stringify(loadFilter): ''),
1050
- es.value[es.value.length - 1]?.id?.toString()
1051
- )
1056
+ setLastId(fetchKey, es.value[es.value.length - 1]?.id?.toString())
1052
1057
  const createdAt: any = (es.value[es.value.length - 1] as any).createdAt;
1053
1058
  if (typeof createdAt === 'string' || createdAt instanceof Date) {
1054
1059
  setLastDate(modelName, new Date(createdAt))
@@ -1074,49 +1079,58 @@ export const useListStateHook = <T extends { id: string | number }, ADD extends
1074
1079
  const reload: ListUpdateMethods <T, ADD>['reload'] = useCallback(options => load(true, { ...options, reloading: true }), [load])
1075
1080
 
1076
1081
  useEffect(() => {
1082
+ if (options?.unbounceMS) {
1083
+ const i = setTimeout(() => load(false), options.unbounceMS)
1084
+ return () => { clearTimeout(i) }
1085
+ }
1077
1086
  load(false)
1078
- }, [load])
1087
+ }, [load, options?.unbounceMS])
1079
1088
 
1080
- useEffect(() => {
1081
- if (didFetch(modelName + 'socket')) return
1082
- setFetched(modelName + 'socket', true, false)
1083
-
1084
- session.handle_events({
1085
- // create, update, and delete must go in this order
1086
- // e.g. to ensure delete events are processed last, so deleted records don't appear as created
1087
- [`created-${modelName}`]: addLocalElements,
1088
- [`updated-${modelName}`]: es => {
1089
- const idToUpdates = {} as Indexable<Partial<T>>
1090
- for (const { id, ...e } of es) {
1091
- idToUpdates[id] = e
1092
- }
1093
- updateLocalElements(idToUpdates)
1094
- },
1095
- [`deleted-${modelName}`]: removeLocalElements,
1096
- })
1089
+ const doneLoading = useCallback((key="id") => {
1090
+ const unfileteredCase = didFetch(key + modelName + DONE_LOADING_TOKEN)
1091
+ if (unfileteredCase) return true
1097
1092
 
1098
- return () => {
1099
- setFetched(modelName + 'socket', false, false)
1100
- session.removeListenersForEvent(`created-${modelName}`)
1101
- session.removeListenersForEvent(`updated-${modelName}`)
1102
- session.removeListenersForEvent(`deleted-${modelName}`)
1103
- }
1104
- }, [session, addLocalElement, updateLocalElements, removeLocalElements, modelName, didFetch])
1093
+ const sort = options?.sort
1094
+ const sortBy = options?.sortBy
1105
1095
 
1106
- const doneLoading = useCallback((key="id") => (
1107
- didFetch(key + modelName + DONE_LOADING_TOKEN)
1108
- ), [didFetch, modelName])
1096
+ const _filter = options?.loadFilter
1097
+ const filter = (_filter && object_is_empty(_filter)) ? undefined : _filter
1098
+
1099
+ const _mdbFilter = options?.mdbFilter
1100
+ const mdbFilter = (_mdbFilter && _mdbFilter?.$and?.length) ? _mdbFilter : undefined
1101
+
1102
+ const filterKey = (
1103
+ (mdbFilter || filter || sort || sortBy)
1104
+ ? JSON.stringify({ ...mdbFilter, ...filter, sort, sortBy }) + modelName
1105
+ : modelName
1106
+ )
1107
+ if (didFetch(filterKey + DONE_LOADING_TOKEN)) return true
1108
+
1109
+ return false
1110
+ }, [didFetch, modelName, options?.loadFilter, options?.mdbFilter, options?.sort, options?.sortBy])
1109
1111
 
1110
1112
  const loadMore = useCallback(async (loadOptions?: LoadMoreOptions<T>) => {
1111
- const filter = loadOptions?.filter ?? options?.loadFilter
1113
+ const sort = options?.sort
1114
+ const sortBy = options?.sortBy
1115
+
1116
+ const _filter = loadOptions?.filter ?? options?.loadFilter
1117
+ const filter = (_filter && object_is_empty(_filter)) ? undefined : _filter
1118
+
1119
+ const _mdbFilter = loadOptions?.mdbFilter || options?.mdbFilter
1120
+ const mdbFilter = (_mdbFilter && _mdbFilter?.$and?.length) ? _mdbFilter : undefined
1112
1121
 
1113
- const lastId = getLastId(
1114
- modelName + (filter ? JSON.stringify(filter) : ""),
1122
+ const mdbFilterIsActive = (mdbFilter && mdbFilter?.$and?.length)
1123
+ const filterKey = (
1124
+ (mdbFilter || filter || sort || sortBy)
1125
+ ? JSON.stringify({ ...mdbFilter, ...filter, sort, sortBy }) + modelName
1126
+ : modelName
1115
1127
  )
1128
+
1129
+ const lastId = getLastId(filterKey)
1116
1130
  if (!lastId) return
1117
1131
  if (!loadQuery) return
1118
- if (didFetch(modelName + 'lastId' + lastId)) return
1119
- setFetched(modelName + 'lastId' + lastId, true)
1132
+ if (didFetch(filterKey + 'lastId' + lastId)) return
1133
+ setFetched(filterKey + 'lastId' + lastId, true)
1120
1134
 
1121
1135
  // todo: support for updatedAt as well, and more?
1122
1136
  const key = loadOptions?.key ?? 'id'
@@ -1128,18 +1142,19 @@ export const useListStateHook = <T extends { id: string | number }, ADD extends
1128
1142
  lastId,
1129
1143
  limit,
1130
1144
  filter,
1145
+ mdbFilter: mdbFilterIsActive ? mdbFilter : undefined,
1131
1146
  })).then(
1132
1147
  es => {
1133
1148
  if (es.status === LoadingStatus.Loaded) {
1134
- if (es.value.length < limit) {
1149
+ if (es.value.length < limit && !mdbFilter && (!filter || object_is_empty(filter))) {
1135
1150
  setFetched(key + modelName + DONE_LOADING_TOKEN, true)
1136
1151
  }
1152
+ else if (es.value.length < limit) {
1153
+ setFetched(filterKey + DONE_LOADING_TOKEN, true)
1154
+ }
1137
1155
  const newLastId = es.value[es.value.length - 1]?.id?.toString()
1138
1156
  if (newLastId) {
1139
- setLastId(
1140
- modelName + (filter ? JSON.stringify(filter) : ""),
1141
- newLastId
1142
- )
1157
+ setLastId(filterKey, newLastId)
1143
1158
  }
1144
1159
 
1145
1160
  dispatch(slice.actions.addSome({ value: es.value, options: { replaceIfMatch: true, addTo: 'end' } }))
@@ -1148,7 +1163,7 @@ export const useListStateHook = <T extends { id: string | number }, ADD extends
1148
1163
  }
1149
1164
  }
1150
1165
  )
1151
- }, [getLastId, modelName, loadQuery, didFetch, setFetched])
1166
+ }, [getLastId, modelName, loadQuery, didFetch, setFetched, options?.mdbFilter, options?.loadFilter, options?.sort, options?.sortBy, options?.limit, dispatch])
1152
1167
 
1153
1168
  const loadRecentlyCreated = React.useCallback(async () => {
1154
1169
  if (!loadQuery) return []
@@ -1206,10 +1221,12 @@ export type HookOptions<T> = {
1206
1221
  sortBy?: SortBy,
1207
1222
  limit?: number,
1208
1223
  loadFilter?: ReadFilter<T>,
1224
+ mdbFilter?: Record<string, any>,
1209
1225
  refetchInMS?: number,
1210
1226
  dontFetch?: boolean,
1211
1227
  addTo?: AddOptions['addTo'],
1212
1228
  onBulkRead?: (matches: T[]) => void,
1229
+ unbounceMS?: number,
1213
1230
  }
1214
1231
 
1215
1232
  export const useChatRoomDisplayInfo = (roomId: string, options={} as HookOptions<ChatRoomDisplayInfo>) => {
@@ -2896,14 +2913,35 @@ export const useCalendarEventsForUser = (options={} as HookOptions<CalendarEvent
2896
2913
 
2897
2914
  const [eventsLoading, { addLocalElements, filtered }] = useCalendarEvents()
2898
2915
 
2899
- const loadEvents = useCallback((options?: LoadEventOptions) => {
2916
+ const loadEvents = useCallback(async (options?: LoadEventOptions & { to?: Date }) => {
2900
2917
  const key = JSON.stringify(options ?? {})
2901
2918
  if (loadedRef.current[key]) return
2902
2919
  loadedRef.current[key] = Date.now()
2903
2920
 
2904
- fetchEvents(options)
2905
- .then(es => addLocalElements(es, { replaceIfMatch: true }))
2906
- .catch(console.error)
2921
+ const load = (from?: Date) => (
2922
+ console.log('Loading events from', from, 'to', options?.to),
2923
+ fetchEvents({ ...options, from })
2924
+ .then(es => addLocalElements(es, { replaceIfMatch: true }))
2925
+ .catch(console.error)
2926
+ )
2927
+
2928
+ let i = 0
2929
+ let from = options?.from
2930
+ while (i < 10) {
2931
+ i++
2932
+ const loaded = await load(from)
2933
+
2934
+ const to = options?.to
2935
+ if (!to) { break }
2936
+ if (!loaded) { break }
2937
+ if (loaded.length === 0) { break }
2938
+
2939
+ if (loaded.find(e => new Date(e.startTimeInMS).getTime() > to.getTime())) {
2940
+ break
2941
+ }
2942
+ // else continue to loop (up to 10 times) until we find events after the to date
2943
+ from = new Date(Math.max(...loaded.map(e => e.startTimeInMS)))
2944
+ }
2907
2945
  }, [session, loadedRef, fetchEvents, addLocalElements])
2908
2946
 
2909
2947
  return [eventsLoading, { loadEvents, filtered }] as const
package/src/table.tsx CHANGED
@@ -39,6 +39,7 @@ import { PRIMARY_HEX } from "@tellescope/constants"
39
39
  import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
40
40
  import { Autocomplete } from "@mui/material"
41
41
  import { ListQueryQualifier, SortingField } from "@tellescope/types-models"
42
+ import { LoadingButton } from "./forms"
42
43
  // import DragHandleIcon from '@mui/icons-material/DragHandle';
43
44
 
44
45
  const LIGHT_GRAY = "#fafafa"
@@ -150,6 +151,7 @@ export interface TableHeaderProps<T extends Item> extends Styled, HorizontalPadd
150
151
  filterSuggestions: Record<string, string[]>,
151
152
  minColumnWidth?: number,
152
153
  columnResizeZIndex?: number,
154
+ headerHeight?: number,
153
155
  }
154
156
  export const TableHeader = <T extends Item>({
155
157
  fields,
@@ -171,6 +173,7 @@ export const TableHeader = <T extends Item>({
171
173
  filterSuggestions,
172
174
  minColumnWidth=75,
173
175
  columnResizeZIndex=1000,
176
+ headerHeight=ROW_HEIGHT,
174
177
  } : TableHeaderProps<T>) => {
175
178
  const [openFilter, setOpenFilter] = useState(-1)
176
179
  const [startX, setStartX] = useState(0)
@@ -241,7 +244,7 @@ export const TableHeader = <T extends Item>({
241
244
 
242
245
  <Flex alignItems="center" style={{
243
246
  paddingLeft: horizontalPadding, paddingRight: horizontalPadding,
244
- minHeight: ROW_HEIGHT,
247
+ minHeight: headerHeight,
245
248
  backgroundColor: DARK_GRAY,
246
249
  ...style
247
250
  }}>
@@ -405,6 +408,7 @@ export interface TableRowProps<T extends Item> extends Styled, HorizontalPadded,
405
408
  textStyle?: CSSProperties,
406
409
  widthOffsets: Record<string, number>,
407
410
  minColumnWidth?: number,
411
+ rowHeight?: number,
408
412
  }
409
413
  export const TableRow = <T extends Item>({
410
414
  item, indices, fields, onClick, onPress, hover,
@@ -422,13 +426,14 @@ export const TableRow = <T extends Item>({
422
426
  allowUnselectItemsAfterSelectAll,
423
427
  setAllSelected,
424
428
  minColumnWidth=75,
429
+ rowHeight=ROW_HEIGHT,
425
430
  } : TableRowProps<T>) => (
426
431
  <WithHover hoveredColor={hoveredColor ?? GRAY} notHoveredColor={notHoveredColor} disabled={!hover} flex>
427
432
  <Flex flex={1} alignItems="center"
428
433
  onClick={() => (onClick ?? onPress)?.(item)}
429
434
  style={{
430
435
  paddingLeft: horizontalPadding, paddingRight: horizontalPadding,
431
- minHeight: ROW_HEIGHT,
436
+ minHeight: rowHeight,
432
437
  ...style,
433
438
  backgroundColor: undefined, // leave in parent component
434
439
  }}
@@ -717,6 +722,7 @@ export interface TableProps<T extends Item> extends WithTitle, WithHeader<T>, Wi
717
722
  titleStyle?: React.CSSProperties,
718
723
  // description?: string,
719
724
  titleActionsComponent?: React.ReactNode,
725
+ titleComponentHeight?: number,
720
726
  noPaper?: boolean,
721
727
  emptyText?: string,
722
728
  emptyComponent?: React.ReactElement,
@@ -744,10 +750,13 @@ export interface TableProps<T extends Item> extends WithTitle, WithHeader<T>, Wi
744
750
  refreshFilterSuggestionsKey?: number,
745
751
  minColumnWidth?: number,
746
752
  columnResizeZIndex?: number,
753
+ rowHeight?: number,
754
+ headerHeight?: number,
747
755
  }
748
756
  export const Table = <T extends Item>({
749
757
  items,
750
758
  emptyText,
759
+ titleComponentHeight,
751
760
  emptyComponent,
752
761
  noPaper,
753
762
  pageOptions={ paginated: true },
@@ -762,7 +771,7 @@ export const Table = <T extends Item>({
762
771
  doneLoading,
763
772
  loadMoreOptions,
764
773
  // onClearFilter,
765
- filterCounts,
774
+ filterCounts: _filterCounts,
766
775
 
767
776
  title,
768
777
  titleStyle,
@@ -772,11 +781,13 @@ export const Table = <T extends Item>({
772
781
  renderTitleComponent,
773
782
  fields,
774
783
  HeaderComponent=TableHeader,
784
+ headerHeight,
775
785
  hover,
776
786
  hoveredColor,
777
787
  RowComponent=TableRow,
778
788
  footerStyle='numbered',
779
789
  FooterComponent=footerStyle === 'numbered' ? TableFooterNumbered : TableFooter,
790
+ rowHeight,
780
791
 
781
792
  selectable,
782
793
  selected,
@@ -965,6 +976,9 @@ export const Table = <T extends Item>({
965
976
  })
966
977
  }, [sorted, localFilters, fields])
967
978
 
979
+ // make sure filterCounts incorporates column filters whose state is in Table, not parent component
980
+ const filterCounts = (_filterCounts && !paginated) ? { ..._filterCounts, filtered: filtered.length } : undefined
981
+
968
982
  const headerFilterIsActive = (
969
983
  !!(fields.find(f => f.filterIsActive) || localFilters.find(f => f?.query))
970
984
  )
@@ -1039,6 +1053,7 @@ export const Table = <T extends Item>({
1039
1053
  virtualization={virtualization}
1040
1054
  header={fields && HeaderComponent && fields.length > 0 && (items.length > 0 || headerFilterIsActive) && (
1041
1055
  <HeaderComponent selectable={selectable} allSelected={allSelected} allowUnselectItemsAfterSelectAll={allowUnselectItemsAfterSelectAll}
1056
+ headerHeight={headerHeight}
1042
1057
  setAllSelected={v => {
1043
1058
  setAllSelected?.(v)
1044
1059
  if (v) {
@@ -1080,14 +1095,26 @@ export const Table = <T extends Item>({
1080
1095
  // renderProps={{ horizontalPadding }}
1081
1096
  emptyText={emptyComponent ?? (
1082
1097
  (emptyText || headerFilterIsActive)
1083
- ? <Typography style={{ padding: horizontalPadding }}>
1098
+ ? (
1099
+ <>
1100
+ <Typography style={{ padding: horizontalPadding }}>
1084
1101
  {emptyText || 'No results found the current filter'}
1085
1102
  </Typography>
1103
+
1104
+ {loadMore && !doneLoading?.() &&
1105
+ <div style={{ paddingLeft: horizontalPadding, paddingBottom: horizontalPadding }}>
1106
+ <LoadingButton submitText="Load Older Data" submittingText="Loading..." onClick={loadMore}
1107
+ variant="outlined" style={{ width: 200, textAlign: 'center', marginTop: 10 }}
1108
+ />
1109
+ </div>
1110
+ }
1111
+ </>
1112
+ )
1086
1113
  : undefined
1087
1114
  )
1088
1115
  }
1089
1116
  Item={({ item, index }) => ( // index within this list, e.g. a single page
1090
- <RowComponent widthOffsets={widthOffsets}
1117
+ <RowComponent widthOffsets={widthOffsets} rowHeight={rowHeight}
1091
1118
  selectable={selectable} selected={selected} setSelected={setSelected} allSelected={allSelected} setAllSelected={setAllSelected} allowUnselectItemsAfterSelectAll={allowUnselectItemsAfterSelectAll}
1092
1119
  key={item.id} item={item}
1093
1120
  indices={{