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,195 @@
1
+ import React, { Component } from 'react'
2
+ import { LocalizationProvider } from '@mui/x-date-pickers'
3
+ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
4
+ import { Switch, Typography, TextField, styled, Box, TextFieldProps } from '@mui/material'
5
+ import { dayjsCustom, mergeObjects, tryParseIntRequired } from '../utils'
6
+ import { IFormInputBase } from './types'
7
+ import { getErrorMessage } from './helper'
8
+
9
+ const defaultFormatString = 'MM-DD-YYYY'
10
+
11
+ const dateExpiredClasses = {
12
+ root: 'DateExpired-root',
13
+ control: 'DateExpired-control',
14
+ label: 'DateExpired-label',
15
+ labelSwitch: 'DateExpired-labelSwitch',
16
+ input: 'DateExpired-input',
17
+ switch: 'DateExpired-switch'
18
+ }
19
+
20
+ interface ISlots<T> {
21
+ /** @default string */
22
+ type?: 'number' | 'string'
23
+ textFieldProps?: Partial<TextFieldProps>
24
+ switchChecked?: boolean
25
+ switchCheckedGetter?: (value: any, model?: Partial<T>) => boolean
26
+ }
27
+
28
+ interface IProps<T> extends IFormInputBase<T> {
29
+ slots?: ISlots<T>
30
+ }
31
+
32
+ interface IState {
33
+ numberOfDays: number
34
+ switchChecked: boolean
35
+ }
36
+
37
+ function CreateDateExpired<T>(params?: ISlots<T>): React.ComponentType<IProps<T>> {
38
+ class DateExpired extends Component<IProps<T>, IState> {
39
+ defaultNumberOfDays: number = 30
40
+ private id
41
+ constructor(props: IProps<T>) {
42
+ super(props)
43
+ this.state = {
44
+ numberOfDays: this.getNumberOfDays(),
45
+ switchChecked: this.slots.switchChecked
46
+ }
47
+ this.id = new Date().getTime().toString()
48
+ }
49
+
50
+ get slots(): ISlots<T> & { switchChecked: boolean } {
51
+ const { switchChecked, switchCheckedGetter } = this.props.slots ?? {}
52
+ const obj = mergeObjects(params, this.props.slots)
53
+ let check = (this.defaulValue ? !!this.defaulValue : switchChecked) ?? true
54
+ if (switchCheckedGetter) check = switchCheckedGetter(this.defaulValue, this.props.data)
55
+ return { ...obj, switchChecked: check }
56
+ }
57
+
58
+ get defaulValue(): string {
59
+ const { data, name } = this.props
60
+ return this.props.defaultValue ?? (!!data && !!name ? data[name]?.toString() : undefined)
61
+ }
62
+
63
+ get defaultValueInput(): string | number {
64
+ if (this.slots.type === 'number') {
65
+ return this.state.numberOfDays
66
+ } else {
67
+ return this.getOffsetDate(this.state.numberOfDays, 'YYYY-MM-DDTHH:mm:ss.sssZ')
68
+ }
69
+ }
70
+
71
+ getNumberOfDays = (): number => {
72
+ if (this.slots.type === 'number') {
73
+ return tryParseIntRequired(this.defaulValue, this.defaultNumberOfDays)
74
+ } else {
75
+ return this.getDaysUntilDate(this.defaulValue, this.defaultNumberOfDays)
76
+ }
77
+ }
78
+
79
+ //#region Render
80
+ render() {
81
+ return (
82
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
83
+ <Wrap className={dateExpiredClasses.root}>
84
+ <input key={this.defaultValueInput} type='text' hidden name={this.props.name?.toString()} defaultValue={this.defaultValueInput} />
85
+ <TextField {...this.mapTextFieldProps()} />
86
+ <div className={dateExpiredClasses.control}>
87
+ <Typography
88
+ variant='caption'
89
+ className={dateExpiredClasses.labelSwitch}
90
+ {...{ component: 'label', htmlFor: this.id }}
91
+ sx={{ color: this.state.switchChecked ? 'success.main' : '#767676' }}
92
+ >
93
+ {this.state.switchChecked ? 'Use Expiration Date' : 'No Expiration'}
94
+ </Typography>
95
+ <Switch
96
+ id={this.id}
97
+ size='small'
98
+ color='success'
99
+ checked={this.state.switchChecked}
100
+ onChange={(_, checked) => this.setState({ switchChecked: checked })}
101
+ />
102
+ </div>
103
+ </Wrap>
104
+ </LocalizationProvider>
105
+ )
106
+ }
107
+ //#endregion
108
+
109
+ mapTextFieldProps = (): TextFieldProps => {
110
+ const { messageErrors, name, onBlur } = this.props
111
+ const disabled = this.props.disabled || !this.state.switchChecked
112
+ const obj: TextFieldProps = {
113
+ fullWidth: true,
114
+ className: dateExpiredClasses.input,
115
+ label: (
116
+ <span className={dateExpiredClasses.label}>
117
+ Expiry date
118
+ {this.state.switchChecked && <b>{this.getOffsetDate(this.state.numberOfDays)}</b>}
119
+ </span>
120
+ ),
121
+ variant: 'outlined',
122
+ type: 'number',
123
+ disabled: disabled,
124
+ value: this.state.switchChecked ? this.state.numberOfDays : 0,
125
+ onChange: this.handleChange
126
+ }
127
+ if (!!name) {
128
+ obj.onBlur = () => onBlur && onBlur(name)
129
+ const temp = getErrorMessage(messageErrors, name)
130
+ if (temp.error) {
131
+ obj.error = Boolean(temp.error)
132
+ obj.helperText = temp.message ?? ''
133
+ }
134
+ }
135
+ return mergeObjects<TextFieldProps>({}, obj, this.slots.textFieldProps)
136
+ }
137
+
138
+ handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
139
+ const numberOfDays: number = e.target.value != '' ? parseInt(e.target.value) : 0
140
+ this.setState({ numberOfDays })
141
+ }
142
+
143
+ getOffsetDate = (num: number, formatString = defaultFormatString): string => {
144
+ return dayjsCustom().add(num, 'day').format(formatString)
145
+ }
146
+
147
+ getDaysUntilDate = (value?: string, defaultValue = 0): number => {
148
+ try {
149
+ if (!value) return defaultValue
150
+ const target = dayjsCustom(value)
151
+ const today = dayjsCustom()
152
+ const diff = target.diff(today, 'day', true)
153
+ return Math.round(diff)
154
+ } catch {
155
+ return defaultValue
156
+ }
157
+ }
158
+ }
159
+ return DateExpired
160
+ }
161
+ export default CreateDateExpired
162
+
163
+ const Wrap = styled(Box)({
164
+ display: 'flex',
165
+ alignItems: 'center',
166
+ gap: '10px',
167
+ position: 'relative',
168
+ [`.${dateExpiredClasses.switch}`]: {
169
+ margin: 0,
170
+ flex: '0 0 auto'
171
+ },
172
+ [`.${dateExpiredClasses.label}`]: {
173
+ b: {
174
+ color: '#1976d2',
175
+ marginLeft: '8px'
176
+ }
177
+ },
178
+ [`.${dateExpiredClasses.labelSwitch}`]: {
179
+ fontWeight: 600,
180
+ cursor: 'pointer'
181
+ },
182
+ [`.${dateExpiredClasses.control}`]: {
183
+ position: 'absolute',
184
+ top: 0,
185
+ right: 0,
186
+ height: '100%',
187
+ display: 'flex',
188
+ alignItems: 'center'
189
+ },
190
+ [`.${dateExpiredClasses.input}`]: {
191
+ '.MuiInputBase-input': {
192
+ paddingRight: '160px'
193
+ }
194
+ }
195
+ })
@@ -0,0 +1,122 @@
1
+ import React, { Component } from 'react'
2
+ import { styled } from '@mui/material'
3
+ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
4
+ import { DatePicker as MUIDatePicker, LocalizationProvider, DatePickerProps } from '@mui/x-date-pickers'
5
+ import { dayjsCustom, mergeObjects } from '../utils'
6
+ import { IFormInputBase } from './types'
7
+ import { getErrorMessage } from './helper'
8
+ import { Dayjs } from 'dayjs'
9
+
10
+ const formatDefault = 'MM/DD/YYYY'
11
+
12
+ interface ISlots {
13
+ defaultValue?: string
14
+ minDate?: Dayjs
15
+ minDateOffset?: number
16
+ datePickerProps?: DatePickerProps<Dayjs>
17
+ }
18
+
19
+ interface IProps<T> extends IFormInputBase<T> {
20
+ format?: string
21
+ slots?: Omit<ISlots, 'defaultValue'>
22
+ }
23
+
24
+ interface IState {
25
+ value: Dayjs | null
26
+ }
27
+
28
+ const CreateDatePicker = function <T extends Object>(params?: ISlots): React.ComponentType<IProps<T>> {
29
+ class DatePicker extends Component<IProps<T>, IState> {
30
+ private _cachedSlots: ISlots = {}
31
+ constructor(props: IProps<T>) {
32
+ super(props)
33
+ this._cachedSlots = this.mergeSlots(props.slots) ?? {}
34
+ this.state = { value: this.getDefaultValue() }
35
+ }
36
+
37
+ get slots() {
38
+ return this._cachedSlots
39
+ }
40
+
41
+ componentDidUpdate(prevProps: IProps<T>) {
42
+ if (prevProps.slots !== this.props.slots) {
43
+ this._cachedSlots = this.mergeSlots(this.props.slots)
44
+ }
45
+ }
46
+
47
+ getDatePickerProps = (): DatePickerProps<Dayjs> => {
48
+ const delayInDays = params?.minDateOffset ?? 0
49
+ const minDate = delayInDays > 0 ? dayjsCustom().add(delayInDays, 'day').startOf('day') : params?.minDate
50
+ const label = this.props.label ?? this.props.name?.toString()
51
+ const format = this.props.format ?? formatDefault
52
+ const eMessage = getErrorMessage<T>(this.props.messageErrors, this.props.name)
53
+ const obj: DatePickerProps<Dayjs> = {
54
+ label,
55
+ format,
56
+ views: ['day', 'month', 'year'],
57
+ value: this.state.value,
58
+ onChange: this.handleChange,
59
+ disabled: this.props.disabled,
60
+ minDate,
61
+ slotProps: {
62
+ textField: { onBlur: this.handleBlur, fullWidth: true, error: eMessage.error, helperText: eMessage.message, variant: 'outlined' }
63
+ }
64
+ }
65
+ return mergeObjects(obj, params?.datePickerProps, this.slots?.datePickerProps)
66
+ }
67
+
68
+ refInput: HTMLInputElement | null = null
69
+ render() {
70
+ return (
71
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
72
+ <CustomDatePicker {...this.getDatePickerProps()} />
73
+ <input
74
+ hidden
75
+ name={this.props.name?.toString()}
76
+ defaultValue={this.getDefaultValue()?.toDate().toISOString()}
77
+ ref={(ref) => (this.refInput = ref)}
78
+ />
79
+ </LocalizationProvider>
80
+ )
81
+ }
82
+
83
+ private mergeSlots = (currentSlots?: ISlots): ISlots => {
84
+ return mergeObjects<ISlots>({}, params, currentSlots)
85
+ }
86
+
87
+ handleBlur = () => {
88
+ if (!this.props.name) return
89
+ this.props.onBlur && this.props.onBlur(this.props.name)
90
+ }
91
+
92
+ handleChange = (newValue: Dayjs | null) => {
93
+ this.setState({ value: newValue })
94
+ if (this.refInput) {
95
+ this.refInput.value = newValue && !isNaN(newValue.toDate().getTime()) ? newValue.toDate().toISOString() : ''
96
+ }
97
+ setTimeout(this.handleBlur, 50)
98
+ }
99
+
100
+ getDefaultValue = (): Dayjs => {
101
+ try {
102
+ if (!this.props.defaultValue && !this.slots.defaultValue && this.slots.minDateOffset) return dayjsCustom().add(1, 'day').startOf('day')
103
+ if (!this.props.defaultValue && this.slots.minDate) return this.slots.minDate
104
+
105
+ const { data, name } = this.props
106
+ const dValue = this.props.defaultValue ?? this.slots.defaultValue ?? (name ? data?.[name] : '')
107
+ return dValue ? dayjsCustom(dValue.toString()) : dayjsCustom()
108
+ } catch {
109
+ return dayjsCustom()
110
+ }
111
+ }
112
+ }
113
+ return DatePicker
114
+ }
115
+
116
+ export default CreateDatePicker
117
+
118
+ const CustomDatePicker = styled(MUIDatePicker<Dayjs>)({
119
+ '& .MuiInputBase-root::before, & .MuiInputBase-root::after': {
120
+ borderBottom: 'none !important'
121
+ }
122
+ })
@@ -0,0 +1,102 @@
1
+ import React, { Component } from 'react'
2
+ import { Box, SxProps, Theme } from '@mui/material'
3
+ import { IFormBase } from './types'
4
+ import { convertFormDataToJson, GetErrorFromResponse, SingleValidate, ValidateMerge } from './helper'
5
+ import FormValidator, { PartialError, SingleRuleValidate } from './validator'
6
+
7
+ interface IParam<TModel> {
8
+ validate?: FormValidator<Partial<TModel>>
9
+ }
10
+
11
+ interface IProps<TModel> {
12
+ sx?: SxProps<Theme>
13
+ validate?: FormValidator<Partial<TModel>>
14
+ onSubmit: (data: Partial<TModel>, e: React.FormEvent<HTMLFormElement>) => Promise<void>
15
+ }
16
+
17
+ interface IState<TModel> extends Pick<IFormBase<TModel>, 'messageErrors'> {
18
+ modelState?: Partial<TModel>
19
+ }
20
+
21
+ export interface IFormContextBase<TModel> {
22
+ modelState?: Partial<TModel>
23
+ messageErrors: PartialError<TModel>
24
+ onBlur: (keyName: keyof TModel) => void
25
+ setError: (keyName: keyof TModel, message: string) => void
26
+ clearErrorAll: (keyName: keyof TModel) => void
27
+ }
28
+
29
+ const CreateFormBase = function <TModel>(param?: IParam<TModel>) {
30
+ const FormBaseContext = React.createContext<IFormContextBase<TModel>>({} as any)
31
+ class FormBase extends Component<React.PropsWithChildren<IProps<TModel>>, IState<TModel>> {
32
+ refForm: HTMLFormElement | null = null
33
+ constructor(props: IProps<TModel>) {
34
+ super(props)
35
+ this.validate = this.getValidate()
36
+ this.state = { messageErrors: {} }
37
+ }
38
+
39
+ setError = (keyName: keyof TModel, message: string) => {
40
+ const error = { [keyName]: [{ rule: SingleRuleValidate.Custom, message }] }
41
+ this.setState({
42
+ messageErrors: Object.assign({}, this.state.messageErrors, error)
43
+ })
44
+ }
45
+
46
+ clearErrorAll = () => {
47
+ this.setState({ messageErrors: {} })
48
+ }
49
+
50
+ render() {
51
+ const { onBlur, setError, clearErrorAll } = this
52
+ const { modelState, messageErrors } = this.state
53
+ return (
54
+ <Box component='form' sx={this.props.sx} ref={(ref: HTMLFormElement) => (this.refForm = ref)} onSubmit={this.onSubmit}>
55
+ <FormBaseContext.Provider value={{ setError, onBlur, clearErrorAll, modelState, messageErrors }}>
56
+ {this.props.children}
57
+ </FormBaseContext.Provider>
58
+ </Box>
59
+ )
60
+ }
61
+
62
+ private validate: FormValidator<Partial<TModel>>
63
+ onSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
64
+ e.preventDefault()
65
+ const formData = new FormData(e.currentTarget as HTMLFormElement)
66
+ const model = convertFormDataToJson<TModel>(formData)
67
+ this.setState({ modelState: model })
68
+ const messageErrors = this.validate.run(model) as PartialError<TModel>
69
+ if (messageErrors) {
70
+ this.setState({ messageErrors: messageErrors })
71
+ if (Object.keys(messageErrors).length > 0) return
72
+ }
73
+ await this.props.onSubmit(model, e).catch((error) => {
74
+ const messageError = GetErrorFromResponse(error, model)
75
+ this.setState({ messageErrors: { ...this.state.messageErrors, ...(messageError || {}) } })
76
+ })
77
+ }
78
+
79
+ onBlur = (keyName: keyof TModel) => {
80
+ if (!this.refForm) return
81
+ const { messageErrors } = this.state
82
+ const formData = new FormData(this.refForm)
83
+ const model = convertFormDataToJson(formData)
84
+ this.setState({ modelState: model })
85
+ const error = SingleValidate<TModel, Partial<TModel>>(keyName, model, messageErrors, this.validate) || {}
86
+ this.setState({ messageErrors: error as PartialError<TModel> })
87
+ }
88
+
89
+ getValidate = (): FormValidator<Partial<TModel>> => {
90
+ const defaultValidate = new FormValidator<Partial<TModel>>({})
91
+ return ValidateMerge(defaultValidate, param?.validate, this.props.validate)
92
+ }
93
+ }
94
+
95
+ return {
96
+ Form: FormBase,
97
+ Validator: param?.validate,
98
+ Context: FormBaseContext,
99
+ contextMapping: (params: (context: IFormContextBase<TModel>) => JSX.Element) => <FormBaseContext.Consumer>{params}</FormBaseContext.Consumer>
100
+ }
101
+ }
102
+ export default CreateFormBase
@@ -0,0 +1,83 @@
1
+ import React, { Component } from 'react'
2
+ import { Box, Button, Card, DialogActions, DialogContent, DialogTitle, styled } from '@mui/material'
3
+ import { fetchDelay } from '../utils'
4
+ import { IGlobalModalContext, MapGlobalModalContext } from '../api-context'
5
+
6
+ interface IParam<TModel> {
7
+ title?: string
8
+ content: (value?: TModel) => JSX.Element
9
+ colors?: {
10
+ yes?: 'inherit' | 'error' | 'primary' | 'secondary' | 'success' | 'info' | 'warning'
11
+ no?: 'inherit' | 'error' | 'primary' | 'secondary' | 'success' | 'info' | 'warning'
12
+ }
13
+ }
14
+ const CreateFormComfirm = function <TModel = any>(param?: IParam<TModel>) {
15
+ interface IProps {
16
+ data?: TModel
17
+ title?: string
18
+ onSubmit: (value?: TModel) => Promise<void>
19
+ onCancel?: () => void
20
+ }
21
+
22
+ interface IState {
23
+ loading?: boolean
24
+ }
25
+
26
+ class FormConfirm extends Component<React.PropsWithChildren<IProps>, IState> {
27
+ constructor(props: IProps) {
28
+ super(props)
29
+ this.state = { loading: false }
30
+ }
31
+
32
+ render() {
33
+ const content = this.props.children || (param?.content ? param?.content(this.props.data) : undefined)
34
+ const title = this.props.title ?? param?.title ?? 'Are you sure?'
35
+ return (
36
+ <Box sx={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
37
+ {MapGlobalModalContext((context) => (
38
+ <Card>
39
+ <DialogTitle id='alert-dialog-title'>{title}</DialogTitle>
40
+ <DialogContent sx={{ minWidth: '300px' }}>{content}</DialogContent>
41
+ <DialogActions>
42
+ <CustomButton disabled={this.state.loading} color={param?.colors?.no || 'inherit'} onClick={() => this.handleClickNo(context)}>
43
+ No
44
+ </CustomButton>
45
+ <CustomButton color={param?.colors?.yes || 'error'} disabled={this.state.loading} onClick={() => this.handleClickYes(context)}>
46
+ Yes
47
+ </CustomButton>
48
+ </DialogActions>
49
+ </Card>
50
+ ))}
51
+ </Box>
52
+ )
53
+ }
54
+
55
+ handleClickNo = (context: IGlobalModalContext) => {
56
+ context.close()
57
+ this.props.onCancel && this.props.onCancel()
58
+ }
59
+
60
+ handleClickYes = async (context: IGlobalModalContext) => {
61
+ if (!this.props.onSubmit) return
62
+ try {
63
+ this.setState({ loading: true })
64
+ await fetchDelay(() => this.props.onSubmit(this.props.data), 700)
65
+ context.close()
66
+ } catch (error) {
67
+ console.log(error)
68
+ } finally {
69
+ this.setState({ loading: false })
70
+ }
71
+ }
72
+ }
73
+ return FormConfirm
74
+ }
75
+ export default CreateFormComfirm
76
+
77
+ const CustomButton = styled(Button)({
78
+ textTransform: 'capitalize',
79
+ fontWeight: 600,
80
+ '&.MuiButton-colorInherit': {
81
+ color: '#606060!important'
82
+ }
83
+ })
@@ -0,0 +1,170 @@
1
+ import React, { Component, Fragment } from 'react'
2
+ import { BoxProps, Grid, RegularBreakpoints, SxProps, Theme } from '@mui/material'
3
+ import { IFormBase, IFormInputBase } from './types'
4
+ import { MapGlobalModalContext } from '../api-context'
5
+ import { ContentWrap, CreateFormBottomBar } from './create.form-grid-layout.units'
6
+ import FormValidator from './validator'
7
+ import CreateFormBase from './create.form-base'
8
+ import CreateInputBase from './create.input-base'
9
+
10
+ export interface IFormGridLayoutConfig<T> {
11
+ key: keyof T
12
+ label?: string
13
+ placeholder?: string
14
+ reponsives?: RegularBreakpoints
15
+ defaultValue?: any
16
+ inputElement?: React.ComponentType<IFormInputBase<T>>
17
+ }
18
+
19
+ type TSubmitMapping<T> = (value: Partial<T>, oldValue?: T) => Partial<T>
20
+
21
+ interface ISlots<T> {
22
+ action?: React.ComponentType<IFormBase<T>>
23
+ actionBefore?: JSX.Element
24
+ contentBefore?: JSX.Element
25
+ contentAfter?: JSX.Element
26
+ inputVisibility?: Partial<Record<keyof T, boolean>>
27
+ inputDisabled?: Partial<Record<keyof T, boolean>>
28
+ closeState?: { Success?: boolean; Fail?: boolean }
29
+ contentProps?: BoxProps
30
+ }
31
+
32
+ interface IParams<T> extends ISlots<T> {
33
+ configs: IFormGridLayoutConfig<T>[]
34
+ validate: FormValidator<Partial<T>>
35
+ submitMapping?: TSubmitMapping<T>
36
+ }
37
+
38
+ const CreateFormGridLayout = function <T>(params: IParams<T>) {
39
+ const FormBaseInstance = CreateFormBase<T>()
40
+ const BottomBarInstance = CreateFormBottomBar<T>()
41
+ const InputBaseInstance = CreateInputBase<T>({ maxLength: 250 })
42
+
43
+ interface IFormGridLayoutProps {
44
+ data?: T
45
+ onSubmit: (value: Partial<T>, signal?: AbortSignal) => Promise<void>
46
+ onError?: (error: any) => void
47
+ onClose?: () => void
48
+ sx?: SxProps<Theme>
49
+ slots?: ISlots<T>
50
+ }
51
+
52
+ interface IState {
53
+ loadding?: boolean
54
+ }
55
+
56
+ class FormGridLayout extends Component<IFormGridLayoutProps, IState> {
57
+ private abortController?: AbortController
58
+ constructor(props: IFormGridLayoutProps) {
59
+ super(props)
60
+ this.state = { loadding: false }
61
+ }
62
+
63
+ get configMerged() {
64
+ return {
65
+ inputVisibility: this.props.slots?.inputVisibility ?? params.inputVisibility,
66
+ inputDisabled: this.props.slots?.inputDisabled ?? params.inputDisabled
67
+ }
68
+ }
69
+
70
+ render() {
71
+ const BottomBar = params?.action ?? this.props.slots?.action ?? BottomBarInstance
72
+ return MapGlobalModalContext(({ close }) => (
73
+ <FormBaseInstance.Form validate={params.validate} onSubmit={(v) => this.onSubmit(v, close)} sx={this.getSxProps()}>
74
+ {this.renderContent()}
75
+ {FormBaseInstance.contextMapping((context) => (
76
+ <BottomBar data={this.props.data} onBlur={context.onBlur} messageErrors={context.messageErrors} before={this.props.slots?.actionBefore} />
77
+ ))}
78
+ </FormBaseInstance.Form>
79
+ ))
80
+ }
81
+
82
+ renderContent = () => {
83
+ const { slots } = this.props
84
+ return (
85
+ <ContentWrap {...this.props?.slots?.contentProps}>
86
+ {slots?.contentBefore && slots?.contentBefore}
87
+ {params?.contentBefore}
88
+ <Grid container spacing={2}>
89
+ {params.configs.map((config, index) => {
90
+ const visibility: boolean | undefined = this.configMerged.inputVisibility?.[config.key] ? true : undefined
91
+ if (visibility) return <Fragment key={config.key.toString() + index} />
92
+ return (
93
+ <Fragment key={config.key.toString() + index}>
94
+ <Grid item xs={12} {...config.reponsives}>
95
+ {this.renderFormFieldElement(config)}
96
+ </Grid>
97
+ </Fragment>
98
+ )
99
+ })}
100
+ </Grid>
101
+ {params?.contentAfter}
102
+ {slots?.contentAfter && slots?.contentAfter}
103
+ </ContentWrap>
104
+ )
105
+ }
106
+
107
+ renderFormFieldElement = (config: IFormGridLayoutConfig<T>) => {
108
+ const { slots, ...otherProps } = this.props
109
+ const ElementComponent = config.inputElement ?? InputBaseInstance
110
+ const dValue = otherProps.data?.[config.key] ?? config?.defaultValue
111
+ const disabled: boolean | undefined = this.configMerged.inputDisabled?.[config.key] ? true : undefined
112
+ return FormBaseInstance.contextMapping((context) => (
113
+ <ElementComponent
114
+ data={this.props.data}
115
+ onBlur={context.onBlur}
116
+ messageErrors={context.messageErrors}
117
+ name={config.key}
118
+ label={config.label}
119
+ placeholder={config.placeholder}
120
+ disabled={disabled}
121
+ defaultValue={dValue}
122
+ />
123
+ ))
124
+ }
125
+
126
+ loading = () => this.setState({ loadding: true })
127
+
128
+ unloading = () => this.setState({ loadding: false })
129
+
130
+ componentWillUnmount(): void {
131
+ this.abortController?.abort()
132
+ }
133
+
134
+ onSubmit = async (value: Partial<T>, close?: () => void) => {
135
+ const { slots } = this.props
136
+ const mapping = params.submitMapping ?? this.submitMapping
137
+ const data = mapping(value, this.props.data)
138
+ try {
139
+ this.loading()
140
+ this.abortController?.abort()
141
+ this.abortController = new AbortController()
142
+ await this.props.onSubmit(data, this.abortController.signal)
143
+ if (!slots?.closeState || slots.closeState.Success !== false) {
144
+ close && close()
145
+ this.props.onClose && this.props.onClose()
146
+ }
147
+ } catch (error) {
148
+ if (slots?.closeState && slots.closeState.Fail === true) {
149
+ close && close()
150
+ this.props.onClose && this.props.onClose()
151
+ }
152
+ if (this.props.onError) this.props.onError(error)
153
+ } finally {
154
+ this.unloading()
155
+ }
156
+ }
157
+
158
+ submitMapping: TSubmitMapping<T> = (value, oldValue) => {
159
+ return Object.assign({}, oldValue, value)
160
+ }
161
+
162
+ getSxProps = (): SxProps<Theme> => {
163
+ const isLoading = this.state.loadding
164
+ return { ...this.props.sx, opacity: isLoading ? 0.7 : 1, pointerEvents: isLoading ? 'none' : 'auto' }
165
+ }
166
+ }
167
+ return FormGridLayout
168
+ }
169
+
170
+ export default CreateFormGridLayout