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,106 @@
1
+ import React, { Component } from 'react'
2
+ import { GridToolbar, GridToolbarQuickFilter } from '@mui/x-data-grid'
3
+ import { OverridableComponent } from '@mui/material/OverridableComponent'
4
+ import { Box, Button, ButtonProps, styled, SvgIconTypeMap, Typography, TypographyProps } from '@mui/material'
5
+ import { BtnFormCreate } from './ui.buttons'
6
+ import CustomBreadcrumbs, { CustomBreadcrumbConfig } from '../components/custom.breadcrumbs'
7
+ import HelpTooltip from '../components/help-tooltip'
8
+
9
+ export interface IToolbarPannelOptions {
10
+ searchInclude?: string[]
11
+ formCreate?: React.ReactNode
12
+ afterAction?: React.ReactNode
13
+ breadcrumbs?: CustomBreadcrumbConfig[]
14
+ }
15
+
16
+ export interface IToolbarPannelProps extends IToolbarPannelOptions {
17
+ title?: React.ReactNode
18
+ afterTitle?: React.ReactNode
19
+ }
20
+
21
+ class ToolbarPannel extends Component<React.PropsWithChildren<IToolbarPannelProps>> {
22
+ render() {
23
+ return (
24
+ <Box sx={{ padding: '0 6px' }}>
25
+ {this.renderTitle()}
26
+ <Box sx={{ display: 'flex' }}>
27
+ <Box sx={{ flex: 1, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
28
+ <GridToolbar sx={{ padding: 0 }} />
29
+ {this.props.formCreate && <BtnFormCreate>{this.props.formCreate}</BtnFormCreate>}
30
+ {this.props.afterAction}
31
+ </Box>
32
+ <Box sx={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
33
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
34
+ <GridToolbarQuickFilter fullWidth variant='standard' size='small' debounceMs={800} quickFilterParser={(x: any) => [x]} />
35
+ {this.renderEndAdornment()}
36
+ </Box>
37
+ </Box>
38
+ </Box>
39
+ </Box>
40
+ )
41
+ }
42
+
43
+ renderTitle = () => {
44
+ const { title, afterTitle, breadcrumbs } = this.props
45
+
46
+ const isVisible = !!title || !!breadcrumbs || !!afterTitle
47
+ if (!isVisible) return <></>
48
+
49
+ let titleElm = title
50
+ if (typeof title === 'string') titleElm = <Title>{title}</Title>
51
+ if (breadcrumbs) titleElm = <CustomBreadcrumbs value={breadcrumbs} />
52
+
53
+ return (
54
+ <Box sx={{ height: '56px', display: 'flex', alignItems: 'center' }}>
55
+ {titleElm}
56
+ {afterTitle}
57
+ </Box>
58
+ )
59
+ }
60
+
61
+ renderEndAdornment = () => {
62
+ if (!this.props.searchInclude || this.props.searchInclude.length <= 0) return <></>
63
+ return (
64
+ <HelpTooltip title='The search includes' small>
65
+ <SeachHelp>
66
+ {this.props.searchInclude.map((item, index) => (
67
+ <li key={index}>
68
+ <Typography variant='body2'>{item}</Typography>
69
+ </li>
70
+ ))}
71
+ </SeachHelp>
72
+ </HelpTooltip>
73
+ )
74
+ }
75
+ }
76
+
77
+ export default ToolbarPannel
78
+
79
+ interface IToolBarButtonProps extends Omit<ButtonProps, 'startIcon' | 'endIcon'> {
80
+ icon: OverridableComponent<SvgIconTypeMap<{}, 'svg'>> & { muiName: string }
81
+ }
82
+
83
+ export const ToolBarButton = styled(({ icon: Icon, ...props }: IToolBarButtonProps) => (
84
+ <Button variant='contained' color='primary' size='small' startIcon={<Icon fontSize='small' />} {...props} />
85
+ ))({})
86
+
87
+ const SeachHelp = styled('ul')({
88
+ margin: '0 0 0 18px',
89
+ padding: 0,
90
+ li: {
91
+ position: 'relative'
92
+ },
93
+ 'li::after': {
94
+ content: '"►"',
95
+ display: 'inline-block',
96
+ top: '50%',
97
+ transform: 'translateY(-50%)',
98
+ position: 'absolute',
99
+ left: '-18px'
100
+ }
101
+ })
102
+
103
+ const Title = styled((props: TypographyProps) => <Typography noWrap variant='subtitle1' {...props} />)({
104
+ fontWeight: 700,
105
+ flex: 1
106
+ })
@@ -0,0 +1,50 @@
1
+ import { GridColDef, GridLogicOperator, GridPaginationModel, GridSortDirection, GridValidRowModel } from '@mui/x-data-grid'
2
+
3
+ export type CustomGridColDef<T extends GridValidRowModel> = {
4
+ [key in keyof T]?: Omit<GridColDef, 'field'>
5
+ }
6
+
7
+ export type TableQueryDetail = 'filter' | 'quickSearch' | 'sort' | 'pagination' | undefined
8
+
9
+ export interface CustomGridSortItem<T> {
10
+ field: keyof T
11
+ sort: GridSortDirection
12
+ }
13
+
14
+ export type CustomGridSortModel<T> = CustomGridSortItem<T>[]
15
+
16
+ export interface CustomGridFilterItem<T> {
17
+ id?: number | string
18
+ field: keyof T
19
+ value?: any
20
+ operator: GridLogicOperator
21
+ }
22
+
23
+ export interface CustomGridFilterModel<T> {
24
+ items: CustomGridFilterItem<T>[]
25
+ logicOperator?: GridLogicOperator
26
+ quickFilterValues?: any[]
27
+ quickFilterLogicOperator?: GridLogicOperator
28
+ quickFilterExcludeHiddenColumns?: boolean
29
+ }
30
+
31
+ export interface TableQueryParams<T> {
32
+ pagination?: GridPaginationModel
33
+ filter?: CustomGridFilterModel<T>
34
+ sort?: CustomGridSortModel<T>
35
+ loading?: boolean
36
+ detail?: TableQueryDetail
37
+ }
38
+
39
+ export interface TableData<T> {
40
+ items?: T[]
41
+ rowTotal?: number
42
+ }
43
+
44
+ export interface TableState<T> {
45
+ tableData: TableData<T>
46
+ tableQueryParams: TableQueryParams<T>
47
+ tableQueryThunk: TableQueryParams<T>
48
+ }
49
+
50
+ export type TableStateRedux<T, K extends keyof TableState<T> = keyof TableState<T>> = Pick<TableState<T>, K>
@@ -0,0 +1,54 @@
1
+ import React, { FC } from 'react'
2
+ import { Button, IconButton, IconButtonProps, Tooltip } from '@mui/material'
3
+ import ViewListIcon from '@mui/icons-material/ViewList'
4
+ import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'
5
+ import { MapGlobalModalContext, TModalReason } from '../api-context'
6
+
7
+ export const BtnFormCreate: FC<{ children: React.ReactNode }> = (props) => {
8
+ return MapGlobalModalContext((context) => (
9
+ <Tooltip title='Create new'>
10
+ <Button
11
+ size='small'
12
+ onClick={() => context.show({ renderContent: () => props.children })}
13
+ startIcon={<AddCircleOutlineIcon fontSize='small' />}
14
+ sx={{ fontWeight: 600 }}
15
+ variant='contained'
16
+ >
17
+ Create
18
+ </Button>
19
+ </Tooltip>
20
+ ))
21
+ }
22
+
23
+ export interface IBtnFormDetailProps {
24
+ formDetail: React.ReactNode
25
+ onOpenModal?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
26
+ onCloseModal?: (reason?: TModalReason) => void
27
+ }
28
+
29
+ export const BtnFormDetail: FC<IBtnFormDetailProps> = (props) => {
30
+ return MapGlobalModalContext((context) => (
31
+ <Tooltip title='Detail'>
32
+ <IconButton
33
+ onClick={(e) => {
34
+ props.onOpenModal?.(e)
35
+ context.show({ backdropActivated: true, renderContent: () => props.formDetail, onClose: props.onCloseModal })
36
+ }}
37
+ >
38
+ <ViewListIcon />
39
+ </IconButton>
40
+ </Tooltip>
41
+ ))
42
+ }
43
+
44
+ interface IBtnDetailProps {
45
+ onClick?: IconButtonProps['onClick']
46
+ }
47
+
48
+ export const BtnDetail: FC<IBtnDetailProps> = (props) => (
49
+ <Tooltip title='Detail'>
50
+ <IconButton onClick={props.onClick}>
51
+ <ViewListIcon />
52
+ </IconButton>
53
+ </Tooltip>
54
+ )
@@ -0,0 +1,189 @@
1
+ import React, { FC, useEffect, useState } from 'react'
2
+ import { Box, BoxProps, Chip, Divider, IconButton, styled, SxProps, Theme, Tooltip, Typography, TypographyProps } from '@mui/material'
3
+ import OpenInNewIcon from '@mui/icons-material/OpenInNew'
4
+ import { dayjsCustom, mergeObjects } from '../utils'
5
+ import { HelpTooltipWrap } from '../components/help-tooltip'
6
+ import CopyToClipboard from '../components/copy-to-clipboard'
7
+
8
+ //#region CellImageSmall
9
+ interface CellImageSmallProps {
10
+ value: string
11
+ imageUri?: string
12
+ fallbackSrc?: string
13
+ }
14
+
15
+ export const CellImageSmall: FC<CellImageSmallProps> = (props) => {
16
+ const [imgSrc, setImgSrc] = useState(props.imageUri ?? props.fallbackSrc)
17
+ useEffect(() => {
18
+ setImgSrc(props.imageUri ?? props.fallbackSrc)
19
+ }, [props.imageUri, props.fallbackSrc])
20
+ return (
21
+ <Box sx={{ display: 'flex', alignItems: 'center', height: '100%', gap: '10px' }}>
22
+ <ImageSmall src={imgSrc} onError={() => setImgSrc(props.fallbackSrc)} />
23
+ <Typography variant='body1'>{props.value}</Typography>
24
+ </Box>
25
+ )
26
+ }
27
+
28
+ interface ImageSmallFallbackProps {
29
+ imageUri?: string
30
+ imageFallback?: string
31
+ sx?: SxProps<Theme>
32
+ }
33
+
34
+ export const ImageSmallFallback: FC<ImageSmallFallbackProps> = (props) => {
35
+ const [imgSrc, setImgSrc] = useState(props.imageUri ?? props.imageFallback)
36
+ useEffect(() => {
37
+ setImgSrc(props.imageUri ?? props.imageFallback)
38
+ }, [props.imageUri, props.imageFallback])
39
+ return <ImageSmall src={imgSrc} onError={() => setImgSrc(props.imageFallback)} sx={props.sx} />
40
+ }
41
+
42
+ const ImageSmall = styled('img')({
43
+ backgroundRepeat: 'no-repeat',
44
+ height: '50px',
45
+ width: '50px',
46
+ backgroundSize: 'contain',
47
+ borderRadius: '8px',
48
+ boxShadow: 'rgba(0, 0, 0, 0.16) 0px 1px 4px'
49
+ })
50
+ //#endregion
51
+
52
+ //#region CellBase
53
+
54
+ export interface CellBaseOptions {
55
+ openInNewTab?: boolean
56
+ beforeLine?: boolean
57
+ copyToClipboard?: boolean
58
+ imageUrl?: string
59
+ imageFallbackSrc?: string
60
+ valueFormatter?: (value?: string) => string
61
+ wrapProps?: BoxProps
62
+ typographyProps?: TypographyProps
63
+ }
64
+
65
+ interface CellBaseProps extends CellBaseOptions {
66
+ value?: string
67
+ valueFormatted?: string
68
+ }
69
+
70
+ export const CellBase: FC<CellBaseProps> = (props) => {
71
+ const value = props.valueFormatted ?? props.value
72
+ const isCopyToClipboard = !!props.copyToClipboard && !!value
73
+ return (
74
+ <CellBaseWrap title={value} {...props.wrapProps}>
75
+ {props.imageUrl && <ImageSmallFallback imageUri={props.imageUrl} sx={{ mr: '10px' }} imageFallback={props.imageFallbackSrc} />}
76
+ <Typography variant='body2' component='span' noWrap sx={{ flex: 1 }} {...props.typographyProps}>
77
+ {value}
78
+ </Typography>
79
+ {value && props.openInNewTab === true && (
80
+ <Tooltip arrow title='Open new tab'>
81
+ <IconButton component='a' href={value} target='_blank'>
82
+ <OpenInNewIcon fontSize='small' sx={{ color: '#1A75E2' }} />
83
+ </IconButton>
84
+ </Tooltip>
85
+ )}
86
+ {isCopyToClipboard && <CopyToClipboard value={value} />}
87
+ {value && props.beforeLine === true && <Divider flexItem variant='middle' orientation='vertical' />}
88
+ </CellBaseWrap>
89
+ )
90
+ }
91
+
92
+ const CellBaseWrap = styled(({ children, ...props }: BoxProps) => (
93
+ <Box {...props}>
94
+ <div>{children}</div>
95
+ </Box>
96
+ ))({
97
+ flex: 1,
98
+ width: '100%',
99
+ height: '100%',
100
+ position: 'relative',
101
+ top: 0,
102
+ left: 0,
103
+ '& > div': {
104
+ display: 'flex',
105
+ alignItems: 'center',
106
+ width: '100%',
107
+ height: '100%',
108
+ position: 'absolute',
109
+ top: 0,
110
+ left: 0
111
+ }
112
+ })
113
+ //#endregion
114
+
115
+ //#region Chips
116
+ export interface CellChipsProps {
117
+ value?: string
118
+ separator?: string
119
+ maximum?: number
120
+ }
121
+
122
+ export const CellChips: FC<CellChipsProps> = (props) => {
123
+ if (typeof props.value !== 'string') return props.value ?? <></>
124
+
125
+ const list = props.value.split(props?.separator ?? '|').filter((x) => !!x)
126
+ const displayList = props?.maximum ? list.slice(0, props.maximum) : list
127
+ const remainingCount = props?.maximum && list.length > props.maximum ? list.length - props.maximum : 0
128
+
129
+ return (
130
+ <Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '4px', height: '100%' }}>
131
+ {displayList.map((x, i) => (
132
+ <CustomChip key={'key' + i} label={x} size='small' />
133
+ ))}
134
+ {remainingCount > 0 && (
135
+ <HelpTooltipWrap
136
+ title='Artists'
137
+ content={
138
+ <Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '4px' }}>
139
+ {list.map((x, i) => (
140
+ <CustomChip key={'key' + i} label={x} size='small' />
141
+ ))}
142
+ </Box>
143
+ }
144
+ >
145
+ <CustomChip key='remaining' label={`+${remainingCount}`} size='small' />
146
+ </HelpTooltipWrap>
147
+ )}
148
+ </Box>
149
+ )
150
+ }
151
+
152
+ const CustomChip = styled(Chip)({
153
+ lineHeight: 1
154
+ })
155
+ //#endregion
156
+
157
+ //#region Date
158
+ export interface CellDatePropsOwner {
159
+ formatString?: string
160
+ showRelative?: boolean
161
+ typographyProps?: TypographyProps
162
+ styledGetter?: (value: any) => SxProps<Theme>
163
+ }
164
+ export interface CellDateProps extends CellDatePropsOwner {
165
+ value: any
166
+ }
167
+
168
+ export const CellDate: FC<CellDateProps> = (props) => {
169
+ try {
170
+ const showRelative = props?.showRelative ?? false
171
+ const date = dayjsCustom(props.value)
172
+ const formatted = date.format(props.formatString)
173
+ const value = showRelative ? `${formatted} (${dayjsCustom().to(date)})` : formatted
174
+ const sx = props.styledGetter ? props.styledGetter(props.value) : { flex: 1 }
175
+ const mergeTypographyProps = mergeObjects<TypographyProps>({}, props.typographyProps ?? {}, { sx })
176
+ return (
177
+ <Typography variant='body2' component='span' noWrap {...mergeTypographyProps}>
178
+ {value}
179
+ </Typography>
180
+ )
181
+ } catch {
182
+ return (
183
+ <Typography variant='body2' component='span' noWrap>
184
+ format invalid
185
+ </Typography>
186
+ )
187
+ }
188
+ }
189
+ //#endregion
@@ -0,0 +1,13 @@
1
+ import dayjs from 'dayjs'
2
+ import utc from 'dayjs/plugin/utc'
3
+ import timezone from 'dayjs/plugin/timezone'
4
+ import relativeTime from 'dayjs/plugin/relativeTime'
5
+
6
+ dayjs.extend(utc)
7
+ dayjs.extend(timezone)
8
+ dayjs.extend(relativeTime)
9
+
10
+ // Thiết lập múi giờ mặc định theo trình duyệt
11
+ dayjs.tz.setDefault(Intl.DateTimeFormat().resolvedOptions().timeZone)
12
+
13
+ export default dayjs
@@ -0,0 +1,171 @@
1
+ import dayjs from 'dayjs'
2
+
3
+ export const sleep = (sec: number) => new Promise((res) => setTimeout(res, sec))
4
+
5
+ export const fetchDelay = async function <TModel>(action: () => Promise<TModel>, sec: number) {
6
+ const [res] = await Promise.all([action(), sleep(sec)])
7
+ return res
8
+ }
9
+
10
+ export const windowScrollToTop = (options?: ScrollToOptions, delay?: number) => {
11
+ setTimeout(() => {
12
+ window.scrollTo({ top: 0, left: 0, behavior: 'smooth', ...options })
13
+ }, delay || 50)
14
+ }
15
+
16
+ export const mergeClasses = (...classes: string[]) => {
17
+ return classes.join(' ')
18
+ }
19
+
20
+ export const encodeBase64 = (input: string): string => {
21
+ try {
22
+ const utf8Bytes = new TextEncoder().encode(input)
23
+ let binaryString = ''
24
+ utf8Bytes.forEach((byte) => {
25
+ binaryString += String.fromCharCode(byte)
26
+ })
27
+ return btoa(binaryString)
28
+ } catch (error) {
29
+ console.error('Error encoding to base64', error)
30
+ return ''
31
+ }
32
+ }
33
+
34
+ export const decodeBase64 = (encoded: string): string | undefined => {
35
+ try {
36
+ const binaryString = atob(encoded)
37
+ const utf8Bytes = new Uint8Array(binaryString.length)
38
+ for (let i = 0; i < binaryString.length; i++) {
39
+ utf8Bytes[i] = binaryString.charCodeAt(i)
40
+ }
41
+ return new TextDecoder().decode(utf8Bytes)
42
+ } catch (error) {
43
+ console.error('Error decoding base64', error)
44
+ return
45
+ }
46
+ }
47
+
48
+ export const tryParseObject = function <T>(value: any, defaultValue: T): T {
49
+ try {
50
+ if (!value) {
51
+ throw new Error('Value is required!')
52
+ }
53
+ return JSON.parse(value)
54
+ } catch {
55
+ // console.log(error)
56
+ return defaultValue
57
+ }
58
+ }
59
+
60
+ export const tryParseArray = function <T>(value: any, defaultValue: T[] = []): T[] {
61
+ try {
62
+ if (!value) return []
63
+ const parseValue = JSON.parse(value)
64
+ return Array.isArray(parseValue) ? parseValue : []
65
+ } catch {
66
+ return defaultValue
67
+ }
68
+ }
69
+
70
+ export const tryParseIntRequired = function (value: any, defaultValue: number): number {
71
+ try {
72
+ if (!value) return defaultValue
73
+ return parseInt(value)
74
+ } catch {
75
+ return defaultValue
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Simple object check.
81
+ * @param item
82
+ * @returns {boolean}
83
+ */
84
+ const isObject = (obj: any) => {
85
+ return obj && typeof obj === 'object' && !Array.isArray(obj)
86
+ }
87
+
88
+ type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] } | undefined
89
+
90
+ export const mergeObjects = <T>(...objects: DeepPartial<T>[]): T => {
91
+ return objects.reduce((prev, obj) => {
92
+ if (!obj) return prev
93
+ Object.keys(obj).forEach((key) => {
94
+ if (isObject((prev as any)[key]) && isObject((obj as any)[key])) {
95
+ ;(prev as any)[key] = mergeObjects((prev as any)[key], (obj as any)[key])
96
+ } else {
97
+ ;(prev as any)[key] = (obj as any)[key]
98
+ }
99
+ })
100
+ return prev
101
+ }, {} as T) as any
102
+ }
103
+
104
+ //#region Format
105
+ export const formatFileSize = (sizeInKb: number) => {
106
+ if (sizeInKb < 1024) {
107
+ return sizeInKb.toFixed(2) + ' Kb'
108
+ } else if (sizeInKb < 1024 * 1024) {
109
+ return (sizeInKb / 1024).toFixed(2) + ' Mb'
110
+ } else if (sizeInKb < 1024 * 1024 * 1024) {
111
+ return (sizeInKb / (1024 * 1024)).toFixed(2) + ' Gb'
112
+ } else {
113
+ return (sizeInKb / (1024 * 1024 * 1024)).toFixed(2) + ' Tb'
114
+ }
115
+ }
116
+
117
+ export const formatCurrency = (value?: any, prefix = '$ ', suffix = ''): string => {
118
+ let parsedValue
119
+ try {
120
+ parsedValue = parseFloat(value)
121
+ if (isNaN(parsedValue)) parsedValue = 0
122
+ } catch (e) {
123
+ parsedValue = 0
124
+ }
125
+ const roundedValue = parsedValue.toFixed(2)
126
+ const [integerPart, decimalPart] = roundedValue.split('.')
127
+ const formattedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
128
+
129
+ let formattedValue = formattedIntegerPart
130
+ if (decimalPart !== '00') {
131
+ formattedValue = `${formattedIntegerPart}.${decimalPart}`
132
+ }
133
+ return `${prefix}${formattedValue}${suffix}`
134
+ }
135
+
136
+ export const formatNumberWithCommas = (number: number): string => {
137
+ return number.toLocaleString('en-US')
138
+ }
139
+
140
+ export const formatCapitalizeFirstText = (value: string = '') => {
141
+ if (!value) return value
142
+ const [first, ...data] = Array.from(value)
143
+ return `${first.toUpperCase()}${data.join('')}`
144
+ }
145
+
146
+ const formatDatetimeStyle = {
147
+ style1: 'DD/MM/YYYY HH:mm',
148
+ style2: 'MMMM D, YYYY',
149
+ style3: 'MM-DD-YYYY'
150
+ }
151
+
152
+ /**
153
+ * Formats a datetime string using one of the predefined styles.
154
+ *
155
+ * Available format styles:
156
+ * - style1: DD/MM/YYYY HH:mm → e.g., '25/04/2025 14:30'
157
+ * - style2: 'MMMM D, YYYY' → e.g., 'April 25, 2025'
158
+ * - style2: 'MM-DD-YYYY' → e.g., '04-25-2025'
159
+ * @param value - A datetime string (ISO format or any format parsable by dayjs).
160
+ * @param format - Format style key: 'style1' or 'style2'. Defaults to 'style1'.
161
+ * @returns A formatted datetime string, or 'unknown' if the input is invalid or unparsable.
162
+ */
163
+ export const formatDatetime = (value: string, format: keyof typeof formatDatetimeStyle = 'style1'): string => {
164
+ try {
165
+ if (!value) throw new Error()
166
+ return dayjs(value).format(formatDatetimeStyle[format])
167
+ } catch (error) {
168
+ return 'unknown'
169
+ }
170
+ }
171
+ //#endregion
@@ -0,0 +1,7 @@
1
+ export * from './helpers'
2
+
3
+ export { default as JObject } from './json-object'
4
+
5
+ export { default as QueryParam } from './query-param'
6
+
7
+ export { default as dayjsCustom } from './dayjs-config'
@@ -0,0 +1,29 @@
1
+ export default class JObject {
2
+ private value: Record<string, any>
3
+ constructor(value?: any) {
4
+ try {
5
+ if (typeof value === 'string') {
6
+ this.value = JSON.parse(value ?? '{}')
7
+ } else {
8
+ this.value = value ?? {}
9
+ }
10
+ } catch (error) {
11
+ this.value = {}
12
+ }
13
+ }
14
+ static fromJson(value?: any) {
15
+ return new JObject(value)
16
+ }
17
+ toValue = <T extends Record<string, any>, TK extends keyof T = keyof T>(key: TK): T[TK] | undefined => {
18
+ return this.value[key as any]
19
+ }
20
+ toJObject = <T extends Record<string, any>, TK extends keyof T = keyof T>(key: TK): JObject => {
21
+ return new JObject(this.value[key as any])
22
+ }
23
+ setValue = <T extends Record<string, any>, TK extends keyof T = keyof T>(key: TK, value: any) => {
24
+ this.value[key as any] = value
25
+ }
26
+ toObject = <T>(): Partial<T> => {
27
+ return this.value as T
28
+ }
29
+ }
@@ -0,0 +1,34 @@
1
+ import React, { FC, useEffect } from 'react'
2
+ import { NavigateOptions, useNavigate } from 'react-router-dom'
3
+ export interface MFEventNavigate {
4
+ to: string
5
+ options?: NavigateOptions & {
6
+ target?: '_self' | '_blank'
7
+ }
8
+ }
9
+
10
+ export const MFEventProvider: FC = () => {
11
+ const navigate = useNavigate()
12
+
13
+ useEffect(() => {
14
+ const handleNavigate = (e: CustomEvent) => {
15
+ const { to, options } = e.detail as MFEventNavigate
16
+ if (!to) return
17
+ if (options?.target === '_blank') {
18
+ globalThis.open(`${to}`, '_blank')
19
+ return
20
+ }
21
+ navigate(to, options)
22
+ }
23
+ globalThis.addEventListener('mfe:navigate', handleNavigate as EventListener)
24
+ return () => globalThis.removeEventListener('mfe:navigate', handleNavigate as EventListener)
25
+ }, [navigate])
26
+ return <></>
27
+ }
28
+
29
+ export const useMFEvent = () => {
30
+ const navigate = (params: MFEventNavigate) => {
31
+ globalThis.dispatchEvent(new CustomEvent('mfe:navigate', { detail: params }))
32
+ }
33
+ return { navigate }
34
+ }