dinocollab-core 1.0.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 (214) hide show
  1. package/README.md +54 -0
  2. package/dist/_virtual/_rollupPluginBabelHelpers.js +431 -0
  3. package/dist/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  4. package/dist/assets/vector-404265a04f4f9c8be1f.webp +0 -0
  5. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js +46 -0
  6. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js.map +1 -0
  7. package/dist/src/api-context/alert-global.js +151 -0
  8. package/dist/src/api-context/alert-global.js.map +1 -0
  9. package/dist/src/api-context/drawer-global.js +105 -0
  10. package/dist/src/api-context/drawer-global.js.map +1 -0
  11. package/dist/src/api-context/global-modal.js +87 -0
  12. package/dist/src/api-context/global-modal.js.map +1 -0
  13. package/dist/src/api-context/popover-global.js +102 -0
  14. package/dist/src/api-context/popover-global.js.map +1 -0
  15. package/dist/src/api-context/popover.js +86 -0
  16. package/dist/src/api-context/popover.js.map +1 -0
  17. package/dist/src/api-context/ui.units.js +21 -0
  18. package/dist/src/api-context/ui.units.js.map +1 -0
  19. package/dist/src/components/copy-to-clipboard.js +105 -0
  20. package/dist/src/components/copy-to-clipboard.js.map +1 -0
  21. package/dist/src/components/custom.breadcrumbs.js +61 -0
  22. package/dist/src/components/custom.breadcrumbs.js.map +1 -0
  23. package/dist/src/components/help-tooltip.js +91 -0
  24. package/dist/src/components/help-tooltip.js.map +1 -0
  25. package/dist/src/components/image-with-fallback.js +48 -0
  26. package/dist/src/components/image-with-fallback.js.map +1 -0
  27. package/dist/src/components/text-editor.js +117 -0
  28. package/dist/src/components/text-editor.js.map +1 -0
  29. package/dist/src/form/create.autocomplete.chips.js +218 -0
  30. package/dist/src/form/create.autocomplete.chips.js.map +1 -0
  31. package/dist/src/form/create.date-expired.js +201 -0
  32. package/dist/src/form/create.date-expired.js.map +1 -0
  33. package/dist/src/form/create.date-picker.js +125 -0
  34. package/dist/src/form/create.date-picker.js.map +1 -0
  35. package/dist/src/form/create.form-base.js +135 -0
  36. package/dist/src/form/create.form-base.js.map +1 -0
  37. package/dist/src/form/create.form-comfirm.js +119 -0
  38. package/dist/src/form/create.form-comfirm.js.map +1 -0
  39. package/dist/src/form/create.form-grid-layout.js +177 -0
  40. package/dist/src/form/create.form-grid-layout.js.map +1 -0
  41. package/dist/src/form/create.form-grid-layout.units.js +39 -0
  42. package/dist/src/form/create.form-grid-layout.units.js.map +1 -0
  43. package/dist/src/form/create.input-base.js +260 -0
  44. package/dist/src/form/create.input-base.js.map +1 -0
  45. package/dist/src/form/create.input.file.js +74 -0
  46. package/dist/src/form/create.input.file.js.map +1 -0
  47. package/dist/src/form/create.select-simple.js +104 -0
  48. package/dist/src/form/create.select-simple.js.map +1 -0
  49. package/dist/src/form/create.select-with-api.js +271 -0
  50. package/dist/src/form/create.select-with-api.js.map +1 -0
  51. package/dist/src/form/create.text-editor.js +156 -0
  52. package/dist/src/form/create.text-editor.js.map +1 -0
  53. package/dist/src/form/dino-form.js +42 -0
  54. package/dist/src/form/dino-form.js.map +1 -0
  55. package/dist/src/form/helper.js +157 -0
  56. package/dist/src/form/helper.js.map +1 -0
  57. package/dist/src/form/modal-wrapper.js +75 -0
  58. package/dist/src/form/modal-wrapper.js.map +1 -0
  59. package/dist/src/form/validator.js +186 -0
  60. package/dist/src/form/validator.js.map +1 -0
  61. package/dist/src/hooks/index.js +48 -0
  62. package/dist/src/hooks/index.js.map +1 -0
  63. package/dist/src/index.js +26 -0
  64. package/dist/src/index.js.map +1 -0
  65. package/dist/src/redux/create.hoc-lazy.js +67 -0
  66. package/dist/src/redux/create.hoc-lazy.js.map +1 -0
  67. package/dist/src/redux/dino.js +11 -0
  68. package/dist/src/redux/dino.js.map +1 -0
  69. package/dist/src/redux/types.js +9 -0
  70. package/dist/src/redux/types.js.map +1 -0
  71. package/dist/src/redux/ui.error-page.js +80 -0
  72. package/dist/src/redux/ui.error-page.js.map +1 -0
  73. package/dist/src/redux/vector-404.webp.js +4 -0
  74. package/dist/src/redux/vector-404.webp.js.map +1 -0
  75. package/dist/src/table/context.js +12 -0
  76. package/dist/src/table/context.js.map +1 -0
  77. package/dist/src/table/create.action-row.js +135 -0
  78. package/dist/src/table/create.action-row.js.map +1 -0
  79. package/dist/src/table/create.status-cell.js +49 -0
  80. package/dist/src/table/create.status-cell.js.map +1 -0
  81. package/dist/src/table/create.table.js +233 -0
  82. package/dist/src/table/create.table.js.map +1 -0
  83. package/dist/src/table/custom.filter-operators.js +89 -0
  84. package/dist/src/table/custom.filter-operators.js.map +1 -0
  85. package/dist/src/table/dino.js +129 -0
  86. package/dist/src/table/dino.js.map +1 -0
  87. package/dist/src/table/helpers.js +116 -0
  88. package/dist/src/table/helpers.js.map +1 -0
  89. package/dist/src/table/model-filter.js +23 -0
  90. package/dist/src/table/model-filter.js.map +1 -0
  91. package/dist/src/table/toolbar-pannel.js +134 -0
  92. package/dist/src/table/toolbar-pannel.js.map +1 -0
  93. package/dist/src/table/ui.buttons.js +60 -0
  94. package/dist/src/table/ui.buttons.js.map +1 -0
  95. package/dist/src/table/ui.units.js +201 -0
  96. package/dist/src/table/ui.units.js.map +1 -0
  97. package/dist/src/utils/dayjs-config.js +12 -0
  98. package/dist/src/utils/dayjs-config.js.map +1 -0
  99. package/dist/src/utils/helpers.js +197 -0
  100. package/dist/src/utils/helpers.js.map +1 -0
  101. package/dist/src/utils/json-object.js +38 -0
  102. package/dist/src/utils/json-object.js.map +1 -0
  103. package/dist/src/utils/query-param.js +172 -0
  104. package/dist/src/utils/query-param.js.map +1 -0
  105. package/package.json +52 -0
  106. package/rollup.config.js +39 -0
  107. package/src/@types/global.d.ts +5 -0
  108. package/src/api-context/alert-global.tsx +174 -0
  109. package/src/api-context/drawer-global.tsx +116 -0
  110. package/src/api-context/global-modal.tsx +109 -0
  111. package/src/api-context/index.ts +13 -0
  112. package/src/api-context/popover-global.tsx +107 -0
  113. package/src/api-context/popover.tsx +89 -0
  114. package/src/api-context/ui.units.tsx +10 -0
  115. package/src/components/copy-to-clipboard.tsx +86 -0
  116. package/src/components/custom.breadcrumbs.tsx +67 -0
  117. package/src/components/help-tooltip.tsx +75 -0
  118. package/src/components/image-with-fallback.tsx +51 -0
  119. package/src/components/index.tsx +1 -0
  120. package/src/components/input-debounce-timer.tsx +138 -0
  121. package/src/components/loading-buttons.tsx +35 -0
  122. package/src/components/text-editor.preview.tsx +30 -0
  123. package/src/components/text-editor.tsx +125 -0
  124. package/src/form/README.md +55 -0
  125. package/src/form/create.autocomplete.chips.tsx +199 -0
  126. package/src/form/create.date-expired.tsx +195 -0
  127. package/src/form/create.date-picker.tsx +122 -0
  128. package/src/form/create.form-base.tsx +102 -0
  129. package/src/form/create.form-comfirm.tsx +83 -0
  130. package/src/form/create.form-grid-layout.tsx +170 -0
  131. package/src/form/create.form-grid-layout.units.tsx +37 -0
  132. package/src/form/create.input-base.tsx +222 -0
  133. package/src/form/create.input.file.tsx +76 -0
  134. package/src/form/create.select-simple.tsx +101 -0
  135. package/src/form/create.select-with-api.tsx +213 -0
  136. package/src/form/create.text-editor.tsx +161 -0
  137. package/src/form/dino-form.tsx +40 -0
  138. package/src/form/helper.ts +132 -0
  139. package/src/form/index.ts +12 -0
  140. package/src/form/modal-wrapper.tsx +75 -0
  141. package/src/form/types.ts +16 -0
  142. package/src/form/validator.ts +202 -0
  143. package/src/hooks/index.ts +44 -0
  144. package/src/index.ts +7 -0
  145. package/src/lab/create.autocomplete.simple.tsx +57 -0
  146. package/src/lab/create.dino-store.ts +59 -0
  147. package/src/lab/create.multi-select-dropdown.tsx +189 -0
  148. package/src/lab/create.select-mul-with-api/index.tsx +271 -0
  149. package/src/lab/create.select-mul-with-api/table-custom.tsx +194 -0
  150. package/src/lab/create.select-mul-with-api/types.ts +26 -0
  151. package/src/lab/create.select-mul-with-api/ui.units.tsx +163 -0
  152. package/src/lab/filter-bar/base.tsx +162 -0
  153. package/src/lab/filter-bar/create.filter-bar.tsx +190 -0
  154. package/src/lab/filter-bar/create.filter-menu.tsx +156 -0
  155. package/src/lab/filter-bar/create.filter-panel.tsx +95 -0
  156. package/src/lab/filter-bar/create.filtered.tsx +41 -0
  157. package/src/lab/filter-bar/create.sort-menu.tsx +43 -0
  158. package/src/lab/filter-bar/demo.tsx +50 -0
  159. package/src/lab/filter-bar/index.ts +6 -0
  160. package/src/lab/filter-bar/types.ts +105 -0
  161. package/src/lab/filter-bar/ui.units.tsx +70 -0
  162. package/src/lab/grafana-dashboard/configs.ts +43 -0
  163. package/src/lab/grafana-dashboard/date-time-range/absolute-time-rage.tsx +137 -0
  164. package/src/lab/grafana-dashboard/date-time-range/helpers.ts +126 -0
  165. package/src/lab/grafana-dashboard/date-time-range/index.tsx +62 -0
  166. package/src/lab/grafana-dashboard/date-time-range/menu-wrap.tsx +101 -0
  167. package/src/lab/grafana-dashboard/date-time-range/quick-ranges.tsx +161 -0
  168. package/src/lab/grafana-dashboard/date-time-range/types.ts +9 -0
  169. package/src/lab/grafana-dashboard/date-time-range/units.tsx +18 -0
  170. package/src/lab/grafana-dashboard/helper.ts +25 -0
  171. package/src/lab/grafana-dashboard/hooks.tsx +79 -0
  172. package/src/lab/grafana-dashboard/icons.tsx +67 -0
  173. package/src/lab/grafana-dashboard/index.tsx +120 -0
  174. package/src/lab/grafana-dashboard/top-bar.tsx +62 -0
  175. package/src/lab/grafana-dashboard/top-bar.types.ts +5 -0
  176. package/src/lab/grafana-dashboard/types.ts +8 -0
  177. package/src/lab/media-player.core1.tsx +273 -0
  178. package/src/lab/media-player.muted.tsx +62 -0
  179. package/src/lab/media-player.units.ts +80 -0
  180. package/src/lab/table-grid/create.table-grid.tsx +183 -0
  181. package/src/lab/table-grid/demo.tsx +53 -0
  182. package/src/lab/table-grid/dino.tsx +8 -0
  183. package/src/lab/table-grid/helpers.tsx +11 -0
  184. package/src/lab/table-grid/index.ts +3 -0
  185. package/src/lab/table-grid/item-actions.tsx +138 -0
  186. package/src/lab/table-grid/toolbar-pannel.tsx +98 -0
  187. package/src/lab/table-grid/types.ts +68 -0
  188. package/src/redux/create.hoc-lazy.tsx +80 -0
  189. package/src/redux/dino.ts +9 -0
  190. package/src/redux/index.ts +6 -0
  191. package/src/redux/types.ts +27 -0
  192. package/src/redux/ui.error-page.tsx +62 -0
  193. package/src/redux/ui.units.tsx +41 -0
  194. package/src/redux/vector-404.webp +0 -0
  195. package/src/table/context.tsx +16 -0
  196. package/src/table/create.action-row.tsx +91 -0
  197. package/src/table/create.status-cell.tsx +51 -0
  198. package/src/table/create.table.tsx +239 -0
  199. package/src/table/custom.filter-operators.ts +94 -0
  200. package/src/table/dino.tsx +120 -0
  201. package/src/table/helpers.ts +94 -0
  202. package/src/table/index.ts +13 -0
  203. package/src/table/model-filter.ts +43 -0
  204. package/src/table/toolbar-pannel.tsx +106 -0
  205. package/src/table/types.ts +50 -0
  206. package/src/table/ui.buttons.tsx +54 -0
  207. package/src/table/ui.units.tsx +189 -0
  208. package/src/utils/dayjs-config.ts +13 -0
  209. package/src/utils/helpers.ts +171 -0
  210. package/src/utils/index.ts +7 -0
  211. package/src/utils/json-object.ts +29 -0
  212. package/src/utils/mfe-events.tsx +34 -0
  213. package/src/utils/query-param.ts +129 -0
  214. package/tsconfig.json +20 -0
@@ -0,0 +1,37 @@
1
+ import React, { FC, ReactNode } from 'react'
2
+ import { Box, Button, styled } from '@mui/material'
3
+ import { IFormBase } from './types'
4
+
5
+ export const ContentWrap = styled(Box)({
6
+ padding: '16px 12px 3px',
7
+ maxHeight: `calc(100vh - ${48 * 2 + 12 * 2}px)`,
8
+ overflowY: 'auto',
9
+ overflowX: 'hidden'
10
+ })
11
+
12
+ export function CreateFormBottomBar<T>() {
13
+ interface Props extends IFormBase<T> {
14
+ before?: ReactNode
15
+ }
16
+ const FormBottomBar: FC<Props> = (props) => (
17
+ <FormBottomBarWrap className='bottom-bar'>
18
+ <Box sx={{ flex: 1 }}>{props.before}</Box>
19
+ <Button variant='contained' type='submit' size='small'>
20
+ Submit
21
+ </Button>
22
+ </FormBottomBarWrap>
23
+ )
24
+ return FormBottomBar
25
+ }
26
+
27
+ export const FormBottomBarWrap = styled(Box)({
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ padding: '0 12px',
31
+ boxShadow: 'rgba(145, 158, 171, 0.2) 0px 0px 2px 0px,rgba(145, 158, 171, 0.12) 0px 12px 24px -4px',
32
+ position: 'sticky',
33
+ bottom: 0,
34
+ backgroundColor: '#fff',
35
+ zIndex: 1,
36
+ height: '48px'
37
+ })
@@ -0,0 +1,222 @@
1
+ import React, { Component, FC } from 'react'
2
+ import { Box, CircularProgress, Collapse, IconButton, InputAdornment, styled, TextField, TextFieldProps } from '@mui/material'
3
+ import ContentPasteIcon from '@mui/icons-material/ContentPaste'
4
+ import { mergeObjects } from '../utils'
5
+ import { IFormInputBase } from './types'
6
+ import { getErrorMessage } from './helper'
7
+ import ImageWithFallback, { ImageWithFallbackPropsOwner } from '../components/image-with-fallback'
8
+
9
+ export interface InputBaseImage<T> extends Partial<ImageWithFallbackPropsOwner> {
10
+ srcValue?: (value: any, model?: Partial<T>) => string
11
+ element?: React.ComponentType<{ value: any; model?: Partial<T> }>
12
+ mirror?: boolean
13
+ }
14
+
15
+ interface ISlots<T> {
16
+ maxLength?: number
17
+ textFieldProps?: TextFieldProps
18
+ pastenable?: boolean
19
+ imageLeft?: InputBaseImage<T>
20
+ imageRight?: InputBaseImage<T>
21
+ }
22
+
23
+ interface IProps<T> extends IFormInputBase<T> {
24
+ slots?: ISlots<T>
25
+ }
26
+ interface IState {
27
+ value?: string
28
+ }
29
+
30
+ interface IParams<T> extends ISlots<T> {}
31
+
32
+ const CreateInputBase = function <T>(params?: IParams<T>): React.ComponentType<IProps<T>> {
33
+ class InputBase extends Component<IProps<T>, IState> {
34
+ private _cachedSlots: ISlots<T> = {}
35
+ constructor(props: IProps<T>) {
36
+ super(props)
37
+ this._cachedSlots = this.mergeSlots(props.slots) ?? {}
38
+ this.state = { value: this.defaulValue }
39
+ }
40
+
41
+ get slots() {
42
+ return this._cachedSlots
43
+ }
44
+
45
+ get defaulValue(): string {
46
+ const { data, name } = this.props
47
+ return this.props.defaultValue ?? (!!data && !!name ? data[name]?.toString() : undefined)
48
+ }
49
+
50
+ componentDidUpdate(prevProps: IProps<T>) {
51
+ if (prevProps.slots !== this.props.slots) {
52
+ this._cachedSlots = this.mergeSlots(this.props.slots)
53
+ }
54
+ }
55
+
56
+ shouldComponentUpdate(nextProps: Readonly<IProps<T>>): boolean {
57
+ const { name, slots } = this.props
58
+ if (!!name) {
59
+ const currentDataValue = this.props.data?.[name]?.toString() ?? ''
60
+ const nextDataValue = nextProps.data?.[name]?.toString() ?? ''
61
+ if (currentDataValue !== nextDataValue) {
62
+ this.setState({ value: nextDataValue })
63
+ return false
64
+ }
65
+ }
66
+
67
+ if (slots !== nextProps.slots) {
68
+ return true
69
+ }
70
+ return true
71
+ }
72
+
73
+ mapTextFieldProps = (): TextFieldProps => {
74
+ const errorMessage = getErrorMessage(this.props.messageErrors, this.props.name)
75
+ const tfp: TextFieldProps = {
76
+ fullWidth: true,
77
+ variant: 'outlined',
78
+ name: this.props.name?.toString(),
79
+ error: errorMessage.error,
80
+ helperText: errorMessage.message,
81
+ disabled: this.props.disabled,
82
+ onBlur: () => {
83
+ if (!this.props.name) return
84
+ this.props.onBlur && this.props.onBlur(this.props.name)
85
+ },
86
+ label: this.getLabel(),
87
+ placeholder: this.props.placeholder,
88
+ InputLabelProps: !!this.state.value ? { shrink: true } : {},
89
+ value: this.state.value ?? '',
90
+ onChange: this.handleChange
91
+ }
92
+ if (this.slots.pastenable === true) {
93
+ tfp.InputProps = {
94
+ endAdornment: (
95
+ <InputAdornment position='end'>
96
+ <IconButton onClick={this.handlePaste} edge='end'>
97
+ <ContentPasteIcon />
98
+ </IconButton>
99
+ </InputAdornment>
100
+ )
101
+ }
102
+ }
103
+ if (this.slots?.maxLength) tfp.inputProps = { ...tfp.inputProps, maxLength: this.slots.maxLength }
104
+ return mergeObjects<TextFieldProps>({}, tfp, this.slots?.textFieldProps)
105
+ }
106
+
107
+ //#region Render
108
+ render() {
109
+ return (
110
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
111
+ {!!this.props.disabled && <input hidden name={this.props.name?.toString()} defaultValue={this.defaulValue} />}
112
+ {this.renderImageSide('left')}
113
+ <TextField {...this.mapTextFieldProps()} />
114
+ {this.renderImageSide('right')}
115
+ </Box>
116
+ )
117
+ }
118
+
119
+ renderImageSide = (side: 'left' | 'right') => {
120
+ const image = side === 'left' ? this.slots.imageLeft : this.slots.imageRight
121
+ if (!image) return <></>
122
+ const { srcValue, element, alt = 'input-image', fallbackSrc = '', debounceDelay = 700, ...imageOther } = image
123
+ if (element) {
124
+ const Element = element
125
+ return (
126
+ <WrapImage>
127
+ <Element value={this.state.value} model={this.props.data} />
128
+ </WrapImage>
129
+ )
130
+ }
131
+ const src = srcValue ? srcValue(this.state.value, this.props.data) : (imageOther.src ?? this.state.value)
132
+ const imageProps: ImageWithFallbackPropsOwner = { src, alt, fallbackSrc, debounceDelay }
133
+ return (
134
+ <Collapse sx={{ mx: '10px' }} in={!!src} unmountOnExit orientation='horizontal'>
135
+ <WrapImage>
136
+ <ImageWithFallback {...imageOther} {...imageProps} loading={<LoadingCircularProgress />} />
137
+ </WrapImage>
138
+ </Collapse>
139
+ )
140
+ }
141
+ //#endregion
142
+
143
+ private mergeSlots = (currentSlots?: ISlots<T>): ISlots<T> => {
144
+ const base = mergeObjects<ISlots<T>>({}, params, currentSlots)
145
+ const { imageLeft, imageRight } = base
146
+
147
+ // mirror from left to right
148
+ if (imageLeft?.mirror && !imageRight) {
149
+ base.imageRight = { ...imageLeft }
150
+ delete base.imageRight.mirror
151
+ }
152
+ // mirror from right to left
153
+ else if (imageRight?.mirror && !imageLeft) {
154
+ base.imageLeft = { ...imageRight }
155
+ delete base.imageLeft.mirror
156
+ }
157
+
158
+ return base
159
+ }
160
+
161
+ getLabel = () => {
162
+ if (!this.props.label) return
163
+ if (!this.slots?.maxLength) return this.props.label
164
+ return `${this.props.label} (${this.state.value?.length ?? 0}/${this.slots.maxLength})`
165
+ }
166
+
167
+ handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
168
+ this.setState((st) => ({ ...st, value: event.target.value }))
169
+ }
170
+
171
+ handlePaste = async () => {
172
+ try {
173
+ const clipboardText = await navigator.clipboard.readText()
174
+ this.setState({ value: clipboardText })
175
+ } catch (error) {
176
+ console.error('Error clipboard:', error)
177
+ }
178
+ }
179
+ }
180
+ return InputBase
181
+ }
182
+
183
+ export default CreateInputBase
184
+
185
+ // export type InputTextType<T> = ReturnType<typeof CreateInputBase<T>>
186
+
187
+ const LoadingCircularProgress: FC = () => (
188
+ <div>
189
+ <CircularProgress size={24} />
190
+ </div>
191
+ )
192
+
193
+ const WrapImage = styled(Box)({
194
+ fontSize: '1rem',
195
+ height: 'var(--input-base-image-size, 54px)',
196
+ width: 'var(--input-base-image-size, 54px)',
197
+ position: 'relative',
198
+ borderRadius: '8px',
199
+ boxShadow: 'rgba(0, 0, 0, 0.16) 0px 1px 4px',
200
+ overflow: 'hidden',
201
+ '& > img': {
202
+ position: 'absolute',
203
+ top: 0,
204
+ left: 0,
205
+ backgroundRepeat: 'no-repeat',
206
+ height: '100%',
207
+ width: '100%',
208
+ backgroundSize: 'contain'
209
+ },
210
+ '& > div': {
211
+ position: 'absolute',
212
+ top: 0,
213
+ left: 0,
214
+ height: '100%',
215
+ width: '100%',
216
+ background: '#fafafa',
217
+ zIndex: 1,
218
+ display: 'flex',
219
+ alignItems: 'center',
220
+ justifyContent: 'center'
221
+ }
222
+ })
@@ -0,0 +1,76 @@
1
+ import React, { Component } from 'react'
2
+ import { TextField, TextFieldProps } from '@mui/material'
3
+ import { IFormInputBase } from './types'
4
+ import { getErrorMessage } from './helper'
5
+
6
+ interface IParam {
7
+ accept?: string
8
+ multiple?: boolean
9
+ }
10
+
11
+ export default function CreateInputFile<T extends Object>(params?: IParam) {
12
+ interface IProps extends IFormInputBase<T> {
13
+ accept?: string
14
+ multiple?: boolean
15
+ textFieldProps?: TextFieldProps
16
+ }
17
+
18
+ interface IState {}
19
+
20
+ class FormInputFile extends Component<IProps, IState> {
21
+ refInput: HTMLInputElement | null = null
22
+
23
+ render() {
24
+ const eMessage = getErrorMessage(this.props.messageErrors, this.props.name)
25
+ const configs = this.getMergeConfig()
26
+ return (
27
+ <TextField
28
+ inputRef={(ref) => (this.refInput = ref)}
29
+ name={this.props.name?.toString()}
30
+ error={eMessage.error}
31
+ helperText={eMessage.message}
32
+ variant='outlined'
33
+ type='file'
34
+ fullWidth
35
+ onChange={this.handleChange}
36
+ onClick={this.handleClick}
37
+ onFocus={this.handleFocus}
38
+ {...this.props.textFieldProps}
39
+ inputProps={{
40
+ accept: configs.accept,
41
+ multiple: configs.multiple,
42
+ ...this.props.textFieldProps?.inputProps
43
+ }}
44
+ />
45
+ )
46
+ }
47
+
48
+ getMergeConfig = () => ({
49
+ accept: params?.accept ?? this.props.accept,
50
+ multiple: params?.multiple ?? this.props.multiple ?? false
51
+ })
52
+
53
+ handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
54
+ if (!this.props.name) return
55
+ this.props.onBlur && this.props.onBlur(this.props.name)
56
+ }
57
+
58
+ isExploreOpen = false
59
+
60
+ handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {
61
+ this.isExploreOpen = true
62
+ }
63
+
64
+ handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
65
+ if (this.isExploreOpen === true) {
66
+ this.isExploreOpen = false
67
+ if (!this.props.name) return
68
+ this.props.onBlur && this.props.onBlur(this.props.name)
69
+ setTimeout(() => {
70
+ this.refInput && this.refInput.blur()
71
+ }, 50)
72
+ }
73
+ }
74
+ }
75
+ return FormInputFile
76
+ }
@@ -0,0 +1,101 @@
1
+ import React, { Component } from 'react'
2
+ import { Collapse, FormControl, FormHelperText, InputLabel, MenuItem, Select, SelectProps } from '@mui/material'
3
+ import { IFormInputBase } from './types'
4
+ import { getErrorMessage } from './helper'
5
+ import { mergeObjects } from '../utils'
6
+
7
+ export interface ISelectSimpleOption<TModel extends string = string> {
8
+ name: string
9
+ value: TModel
10
+ }
11
+
12
+ interface ISlots {
13
+ selectProps?: Omit<SelectProps, 'variant'>
14
+ }
15
+
16
+ interface IArgs extends ISlots {
17
+ options?: ISelectSimpleOption[]
18
+ }
19
+
20
+ const CreateSelectSimple = function <TModel = any>(args?: IArgs) {
21
+ interface IProps extends Partial<IFormInputBase<TModel>> {
22
+ options?: ISelectSimpleOption[]
23
+ onChange?: (value: ISelectSimpleOption) => void
24
+ slots?: ISlots
25
+ fullWidth?: boolean
26
+ }
27
+ interface IState {
28
+ value?: string
29
+ }
30
+ class SelectSimple extends Component<IProps, IState> {
31
+ constructor(props: IProps) {
32
+ super(props)
33
+ this.state = { value: this.getDefaultValue()?.toString() }
34
+ }
35
+ mapProps = (): SelectProps => {
36
+ const label = this.getLabel()
37
+ const tfp: SelectProps = {
38
+ id: this.props.name?.toString(),
39
+ labelId: this.props.name?.toString() + label,
40
+ name: this.props.name?.toString(),
41
+ label: label,
42
+ defaultValue: this.getDefaultValue(),
43
+ value: this.state.value,
44
+ onChange: (event) => {
45
+ const value: string = event.target.value + ''
46
+ this.setState({ value }, () => {
47
+ if (!!this.props.name) {
48
+ this.props.onBlur && this.props.onBlur(this.props.name)
49
+ }
50
+ const options = this.getOptions()
51
+ const temp = options.find((x) => x.value?.toString() === value)
52
+ if (!temp) return
53
+ this.props.onChange && this.props.onChange(temp)
54
+ })
55
+ },
56
+ disabled: this.props.disabled,
57
+ fullWidth: this.props.fullWidth !== undefined ? this.props.fullWidth : true
58
+ }
59
+ const selectProps = this.props.slots?.selectProps ?? args?.selectProps
60
+ return mergeObjects({}, tfp, selectProps)
61
+ }
62
+ render() {
63
+ const data = this.getOptions()
64
+ const label = this.getLabel()
65
+ const errorMessage = getErrorMessage(this.props.messageErrors, this.props.name)
66
+ return (
67
+ <React.Fragment>
68
+ {!!this.props.disabled && <input hidden name={this.props.name?.toString()} defaultValue={this.getDefaultValue()} />}
69
+ <FormControl fullWidth disabled={this.props.disabled} error={errorMessage.error}>
70
+ <InputLabel id={this.props.name?.toString() + label}>{label}</InputLabel>
71
+ <Select {...this.mapProps()}>
72
+ {data.map((item) => (
73
+ <MenuItem key={item.value} value={item.value}>
74
+ {item.name}
75
+ </MenuItem>
76
+ ))}
77
+ </Select>
78
+ <Collapse in={errorMessage.error}>
79
+ <FormHelperText>{errorMessage.message}</FormHelperText>
80
+ </Collapse>
81
+ </FormControl>
82
+ </React.Fragment>
83
+ )
84
+ }
85
+ getLabel = () => {
86
+ if (!!this.props.label && typeof this.props.label === 'string') return this.props.label
87
+ return this.props.name?.toString() ?? ''
88
+ }
89
+ getDefaultValue = () => {
90
+ const { data, name } = this.props
91
+ const options = this.getOptions()
92
+ return this.props.defaultValue?.toString() ?? (!!data && !!name ? data[name] : undefined) ?? options[0]?.value
93
+ }
94
+ getOptions = (): ISelectSimpleOption[] => {
95
+ return args?.options ?? this.props.options ?? []
96
+ }
97
+ }
98
+ return SelectSimple
99
+ }
100
+ export default CreateSelectSimple
101
+ export type SelectSimpleType<TModel> = ReturnType<typeof CreateSelectSimple<TModel>>
@@ -0,0 +1,213 @@
1
+ import React, { Component } from 'react'
2
+ import { Autocomplete, FilterOptionsState, TextField, TextFieldProps } from '@mui/material'
3
+ import { getErrorMessage } from './helper'
4
+ import { IFormInputBase } from './types'
5
+ import { ApiAlertContext } from '../api-context'
6
+
7
+ export interface ISelectWithApiOption<TOther = any> {
8
+ Id: string
9
+ Name?: string
10
+ Other?: TOther
11
+ }
12
+
13
+ interface IOptions {
14
+ textFieldProps: TextFieldProps
15
+ }
16
+
17
+ export type SelectWithApiFetchData<TOption extends ISelectWithApiOption> = (value?: string, signal?: AbortSignal) => Promise<TOption[]>
18
+
19
+ export interface ISelectWithApiParams<TOption extends ISelectWithApiOption> {
20
+ fetchData?: SelectWithApiFetchData<TOption>
21
+ }
22
+
23
+ interface IProps<TModel, TOption extends ISelectWithApiOption> extends IFormInputBase<TModel>, ISelectWithApiParams<TOption> {
24
+ onChange?: (value: TOption | null) => void
25
+ existedIds?: string[]
26
+ options?: IOptions
27
+ }
28
+
29
+ const CreateSelectWithApi = function <TModel, TOption extends ISelectWithApiOption = ISelectWithApiOption>(params?: ISelectWithApiParams<TOption>) {
30
+ interface IState {
31
+ options: TOption[]
32
+ statusText?: string
33
+ optionSelected: TOption | null
34
+ inputValue: string
35
+ loading?: boolean
36
+ }
37
+ class SelectWithApi extends Component<IProps<TModel, TOption>, IState> {
38
+ abortController = { signalController: new AbortController() }
39
+ refInput: HTMLInputElement | null = null
40
+ existedIds: string[] = []
41
+ constructor(props: IProps<TModel, TOption>) {
42
+ super(props)
43
+ this.state = {
44
+ options: [],
45
+ statusText: 'no items',
46
+ optionSelected: null,
47
+ inputValue: '',
48
+ loading: true
49
+ }
50
+ this.existedIds = props.existedIds ?? []
51
+ }
52
+
53
+ render() {
54
+ const defaultValue = this.getDefaultValue()
55
+ const eMessage = getErrorMessage(this.props.messageErrors, this.props.name)
56
+ return (
57
+ <>
58
+ <Autocomplete
59
+ disabled={this.state.loading || this.props.disabled}
60
+ fullWidth
61
+ noOptionsText={this.state.statusText}
62
+ options={this.state.options}
63
+ getOptionLabel={(x) => x.Name ?? x.Id}
64
+ getOptionKey={(x) => JSON.stringify(x)}
65
+ isOptionEqualToValue={(o, v) => o.Id.toString() === v.Id.toString() && o.Name === v.Name}
66
+ filterOptions={this.fillterOptions}
67
+ // select
68
+ value={this.state.optionSelected}
69
+ onChange={this.handleChange}
70
+ // input
71
+ inputValue={this.state.inputValue}
72
+ onInputChange={this.handleInputChange}
73
+ renderInput={(params) => (
74
+ <TextField
75
+ {...params}
76
+ label={this.getLabel()}
77
+ error={eMessage.error}
78
+ helperText={eMessage.message}
79
+ onBlur={() => {
80
+ if (!this.props.name) return
81
+ this.props.onBlur && this.props.onBlur(this.props.name)
82
+ }}
83
+ {...this.props.options?.textFieldProps}
84
+ />
85
+ )}
86
+ />
87
+ <input ref={(ref) => (this.refInput = ref)} hidden name={this.props.name?.toString()} defaultValue={defaultValue} />
88
+ {this.state.optionSelected?.Other && (
89
+ <input
90
+ hidden
91
+ name={`${this.props.name?.toString()}Other`}
92
+ key={this.state.optionSelected.Id ?? 'key'}
93
+ defaultValue={JSON.stringify(this.state.optionSelected.Other)}
94
+ />
95
+ )}
96
+ </>
97
+ )
98
+ }
99
+
100
+ componentDidMount() {
101
+ this.fetchData()
102
+ }
103
+
104
+ componentWillUnmount(): void {
105
+ this.timer.clear()
106
+ }
107
+
108
+ componentDidUpdate(prevProps: Readonly<IProps<TModel, TOption>>, prevState: Readonly<IState>, snapshot?: any): void {
109
+ if (JSON.stringify(prevProps.existedIds) !== JSON.stringify(this.props.existedIds)) {
110
+ this.existedIds = this.props.existedIds ?? []
111
+ }
112
+ }
113
+
114
+ getFetchDataFunc = (): SelectWithApiFetchData<TOption> => {
115
+ return this.props.fetchData ?? params?.fetchData ?? (() => Promise.resolve([]))
116
+ }
117
+
118
+ timer = {
119
+ _timer: 0,
120
+ _second: 500,
121
+ callback: async (value: string) => {
122
+ try {
123
+ this.abortController.signalController = new AbortController()
124
+ const res = await this.getFetchDataFunc()(value, this.abortController.signalController.signal)
125
+ const options = OptionsFilter(res, this.existedIds)
126
+ this.setState({ options })
127
+ } catch (error) {
128
+ // console.log(error)
129
+ ApiAlertContext.ApiAlert?.PushError('Error from server!')
130
+ } finally {
131
+ this.setState({ statusText: 'no items' })
132
+ }
133
+ },
134
+ start: (text: string) => {
135
+ this.timer.clear()
136
+ this.timer._timer = window.setTimeout(() => this.timer.callback(text), this.timer._second)
137
+ },
138
+ clear: () => {
139
+ this.abortController.signalController.abort()
140
+ clearTimeout(this.timer._timer)
141
+ }
142
+ }
143
+
144
+ fetchData = async () => {
145
+ try {
146
+ const defaultValue = this.getDefaultValue()
147
+ const res = await this.getFetchDataFunc()(defaultValue, this.abortController.signalController.signal)
148
+ if (!Array.isArray(res)) return
149
+ const options = OptionsFilter(res, this.existedIds)
150
+ const optionSelected = options.find((x) => x.Id === defaultValue) ?? null
151
+ this.setState({ options, optionSelected, loading: false })
152
+ return
153
+ } catch (error) {
154
+ // console.log(error)
155
+ ApiAlertContext.ApiAlert?.PushError('Error from server!')
156
+ } finally {
157
+ this.setState({ statusText: 'no items', loading: false })
158
+ }
159
+ }
160
+
161
+ handleChange = (_: React.SyntheticEvent, value: TOption | null) => {
162
+ this.setState({ optionSelected: value })
163
+ if (this.refInput) this.refInput.value = value?.Id ?? ''
164
+ this.props.onChange && this.props.onChange(value)
165
+ }
166
+
167
+ handleInputChange = (_: React.SyntheticEvent, value: string) => {
168
+ const state: Pick<IState, 'inputValue' | 'statusText' | 'loading'> = { inputValue: value }
169
+ if (value === this.state.optionSelected?.Name) {
170
+ this.setState(state)
171
+ return
172
+ }
173
+ const valueFormated = value.trim().toLowerCase()
174
+ const selectedIndex = this.state.options.findIndex((x) => {
175
+ return x.Name?.trim().toLowerCase().includes(valueFormated)
176
+ })
177
+ if (selectedIndex < 0 || valueFormated === '') state.statusText = 'loading...'
178
+ this.setState(state, () => {
179
+ if (selectedIndex < 0 || valueFormated === '') this.timer.start(valueFormated)
180
+ })
181
+ }
182
+
183
+ fillterOptions = (options: TOption[], state: FilterOptionsState<TOption>) => {
184
+ return options.filter((x) => {
185
+ const value = state.inputValue.toLowerCase()
186
+ return x.Id.toLowerCase().includes(value) || x.Name?.toLowerCase().includes(value)
187
+ })
188
+ }
189
+
190
+ getLabel = () => {
191
+ return this.props.label ?? this.props.name?.toString()
192
+ }
193
+
194
+ getDefaultValue = () => {
195
+ if (!this.props.name) return
196
+ return (this.props.defaultValue ?? this.props.data?.[this.props.name])?.toString()
197
+ }
198
+ }
199
+ return SelectWithApi
200
+ }
201
+ export default CreateSelectWithApi
202
+ export type SelectWithApiType<TModel extends ISelectWithApiOption = ISelectWithApiOption> = ReturnType<typeof CreateSelectWithApi<TModel>>
203
+
204
+ function OptionsFilter<O extends ISelectWithApiOption = ISelectWithApiOption>(options: O[], existedId: string[] = []): O[] {
205
+ const ids = new Set<string | number>(existedId)
206
+ return options.reduce<O[]>((a, b) => {
207
+ if (!ids.has(b.Id)) {
208
+ a.push(b)
209
+ ids.add(b.Id)
210
+ }
211
+ return a
212
+ }, [])
213
+ }