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.
- package/README.md +54 -0
- package/dist/_virtual/_rollupPluginBabelHelpers.js +431 -0
- package/dist/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/dist/assets/vector-404265a04f4f9c8be1f.webp +0 -0
- package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js +46 -0
- package/dist/node_modules/.pnpm/@rollup_plugin-typescript@1_d0d2002d9033600b6738d939bd598bc6/node_modules/tslib/tslib.es6.js.map +1 -0
- package/dist/src/api-context/alert-global.js +151 -0
- package/dist/src/api-context/alert-global.js.map +1 -0
- package/dist/src/api-context/drawer-global.js +105 -0
- package/dist/src/api-context/drawer-global.js.map +1 -0
- package/dist/src/api-context/global-modal.js +87 -0
- package/dist/src/api-context/global-modal.js.map +1 -0
- package/dist/src/api-context/popover-global.js +102 -0
- package/dist/src/api-context/popover-global.js.map +1 -0
- package/dist/src/api-context/popover.js +86 -0
- package/dist/src/api-context/popover.js.map +1 -0
- package/dist/src/api-context/ui.units.js +21 -0
- package/dist/src/api-context/ui.units.js.map +1 -0
- package/dist/src/components/copy-to-clipboard.js +105 -0
- package/dist/src/components/copy-to-clipboard.js.map +1 -0
- package/dist/src/components/custom.breadcrumbs.js +61 -0
- package/dist/src/components/custom.breadcrumbs.js.map +1 -0
- package/dist/src/components/help-tooltip.js +91 -0
- package/dist/src/components/help-tooltip.js.map +1 -0
- package/dist/src/components/image-with-fallback.js +48 -0
- package/dist/src/components/image-with-fallback.js.map +1 -0
- package/dist/src/components/text-editor.js +117 -0
- package/dist/src/components/text-editor.js.map +1 -0
- package/dist/src/form/create.autocomplete.chips.js +218 -0
- package/dist/src/form/create.autocomplete.chips.js.map +1 -0
- package/dist/src/form/create.date-expired.js +201 -0
- package/dist/src/form/create.date-expired.js.map +1 -0
- package/dist/src/form/create.date-picker.js +125 -0
- package/dist/src/form/create.date-picker.js.map +1 -0
- package/dist/src/form/create.form-base.js +135 -0
- package/dist/src/form/create.form-base.js.map +1 -0
- package/dist/src/form/create.form-comfirm.js +119 -0
- package/dist/src/form/create.form-comfirm.js.map +1 -0
- package/dist/src/form/create.form-grid-layout.js +177 -0
- package/dist/src/form/create.form-grid-layout.js.map +1 -0
- package/dist/src/form/create.form-grid-layout.units.js +39 -0
- package/dist/src/form/create.form-grid-layout.units.js.map +1 -0
- package/dist/src/form/create.input-base.js +260 -0
- package/dist/src/form/create.input-base.js.map +1 -0
- package/dist/src/form/create.input.file.js +74 -0
- package/dist/src/form/create.input.file.js.map +1 -0
- package/dist/src/form/create.select-simple.js +104 -0
- package/dist/src/form/create.select-simple.js.map +1 -0
- package/dist/src/form/create.select-with-api.js +271 -0
- package/dist/src/form/create.select-with-api.js.map +1 -0
- package/dist/src/form/create.text-editor.js +156 -0
- package/dist/src/form/create.text-editor.js.map +1 -0
- package/dist/src/form/dino-form.js +42 -0
- package/dist/src/form/dino-form.js.map +1 -0
- package/dist/src/form/helper.js +157 -0
- package/dist/src/form/helper.js.map +1 -0
- package/dist/src/form/modal-wrapper.js +75 -0
- package/dist/src/form/modal-wrapper.js.map +1 -0
- package/dist/src/form/validator.js +186 -0
- package/dist/src/form/validator.js.map +1 -0
- package/dist/src/hooks/index.js +48 -0
- package/dist/src/hooks/index.js.map +1 -0
- package/dist/src/index.js +26 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/redux/create.hoc-lazy.js +67 -0
- package/dist/src/redux/create.hoc-lazy.js.map +1 -0
- package/dist/src/redux/dino.js +11 -0
- package/dist/src/redux/dino.js.map +1 -0
- package/dist/src/redux/types.js +9 -0
- package/dist/src/redux/types.js.map +1 -0
- package/dist/src/redux/ui.error-page.js +80 -0
- package/dist/src/redux/ui.error-page.js.map +1 -0
- package/dist/src/redux/vector-404.webp.js +4 -0
- package/dist/src/redux/vector-404.webp.js.map +1 -0
- package/dist/src/table/context.js +12 -0
- package/dist/src/table/context.js.map +1 -0
- package/dist/src/table/create.action-row.js +135 -0
- package/dist/src/table/create.action-row.js.map +1 -0
- package/dist/src/table/create.status-cell.js +49 -0
- package/dist/src/table/create.status-cell.js.map +1 -0
- package/dist/src/table/create.table.js +233 -0
- package/dist/src/table/create.table.js.map +1 -0
- package/dist/src/table/custom.filter-operators.js +89 -0
- package/dist/src/table/custom.filter-operators.js.map +1 -0
- package/dist/src/table/dino.js +129 -0
- package/dist/src/table/dino.js.map +1 -0
- package/dist/src/table/helpers.js +116 -0
- package/dist/src/table/helpers.js.map +1 -0
- package/dist/src/table/model-filter.js +23 -0
- package/dist/src/table/model-filter.js.map +1 -0
- package/dist/src/table/toolbar-pannel.js +134 -0
- package/dist/src/table/toolbar-pannel.js.map +1 -0
- package/dist/src/table/ui.buttons.js +60 -0
- package/dist/src/table/ui.buttons.js.map +1 -0
- package/dist/src/table/ui.units.js +201 -0
- package/dist/src/table/ui.units.js.map +1 -0
- package/dist/src/utils/dayjs-config.js +12 -0
- package/dist/src/utils/dayjs-config.js.map +1 -0
- package/dist/src/utils/helpers.js +197 -0
- package/dist/src/utils/helpers.js.map +1 -0
- package/dist/src/utils/json-object.js +38 -0
- package/dist/src/utils/json-object.js.map +1 -0
- package/dist/src/utils/query-param.js +172 -0
- package/dist/src/utils/query-param.js.map +1 -0
- package/package.json +52 -0
- package/rollup.config.js +39 -0
- package/src/@types/global.d.ts +5 -0
- package/src/api-context/alert-global.tsx +174 -0
- package/src/api-context/drawer-global.tsx +116 -0
- package/src/api-context/global-modal.tsx +109 -0
- package/src/api-context/index.ts +13 -0
- package/src/api-context/popover-global.tsx +107 -0
- package/src/api-context/popover.tsx +89 -0
- package/src/api-context/ui.units.tsx +10 -0
- package/src/components/copy-to-clipboard.tsx +86 -0
- package/src/components/custom.breadcrumbs.tsx +67 -0
- package/src/components/help-tooltip.tsx +75 -0
- package/src/components/image-with-fallback.tsx +51 -0
- package/src/components/index.tsx +1 -0
- package/src/components/input-debounce-timer.tsx +138 -0
- package/src/components/loading-buttons.tsx +35 -0
- package/src/components/text-editor.preview.tsx +30 -0
- package/src/components/text-editor.tsx +125 -0
- package/src/form/README.md +55 -0
- package/src/form/create.autocomplete.chips.tsx +199 -0
- package/src/form/create.date-expired.tsx +195 -0
- package/src/form/create.date-picker.tsx +122 -0
- package/src/form/create.form-base.tsx +102 -0
- package/src/form/create.form-comfirm.tsx +83 -0
- package/src/form/create.form-grid-layout.tsx +170 -0
- package/src/form/create.form-grid-layout.units.tsx +37 -0
- package/src/form/create.input-base.tsx +222 -0
- package/src/form/create.input.file.tsx +76 -0
- package/src/form/create.select-simple.tsx +101 -0
- package/src/form/create.select-with-api.tsx +213 -0
- package/src/form/create.text-editor.tsx +161 -0
- package/src/form/dino-form.tsx +40 -0
- package/src/form/helper.ts +132 -0
- package/src/form/index.ts +12 -0
- package/src/form/modal-wrapper.tsx +75 -0
- package/src/form/types.ts +16 -0
- package/src/form/validator.ts +202 -0
- package/src/hooks/index.ts +44 -0
- package/src/index.ts +7 -0
- package/src/lab/create.autocomplete.simple.tsx +57 -0
- package/src/lab/create.dino-store.ts +59 -0
- package/src/lab/create.multi-select-dropdown.tsx +189 -0
- package/src/lab/create.select-mul-with-api/index.tsx +271 -0
- package/src/lab/create.select-mul-with-api/table-custom.tsx +194 -0
- package/src/lab/create.select-mul-with-api/types.ts +26 -0
- package/src/lab/create.select-mul-with-api/ui.units.tsx +163 -0
- package/src/lab/filter-bar/base.tsx +162 -0
- package/src/lab/filter-bar/create.filter-bar.tsx +190 -0
- package/src/lab/filter-bar/create.filter-menu.tsx +156 -0
- package/src/lab/filter-bar/create.filter-panel.tsx +95 -0
- package/src/lab/filter-bar/create.filtered.tsx +41 -0
- package/src/lab/filter-bar/create.sort-menu.tsx +43 -0
- package/src/lab/filter-bar/demo.tsx +50 -0
- package/src/lab/filter-bar/index.ts +6 -0
- package/src/lab/filter-bar/types.ts +105 -0
- package/src/lab/filter-bar/ui.units.tsx +70 -0
- package/src/lab/grafana-dashboard/configs.ts +43 -0
- package/src/lab/grafana-dashboard/date-time-range/absolute-time-rage.tsx +137 -0
- package/src/lab/grafana-dashboard/date-time-range/helpers.ts +126 -0
- package/src/lab/grafana-dashboard/date-time-range/index.tsx +62 -0
- package/src/lab/grafana-dashboard/date-time-range/menu-wrap.tsx +101 -0
- package/src/lab/grafana-dashboard/date-time-range/quick-ranges.tsx +161 -0
- package/src/lab/grafana-dashboard/date-time-range/types.ts +9 -0
- package/src/lab/grafana-dashboard/date-time-range/units.tsx +18 -0
- package/src/lab/grafana-dashboard/helper.ts +25 -0
- package/src/lab/grafana-dashboard/hooks.tsx +79 -0
- package/src/lab/grafana-dashboard/icons.tsx +67 -0
- package/src/lab/grafana-dashboard/index.tsx +120 -0
- package/src/lab/grafana-dashboard/top-bar.tsx +62 -0
- package/src/lab/grafana-dashboard/top-bar.types.ts +5 -0
- package/src/lab/grafana-dashboard/types.ts +8 -0
- package/src/lab/media-player.core1.tsx +273 -0
- package/src/lab/media-player.muted.tsx +62 -0
- package/src/lab/media-player.units.ts +80 -0
- package/src/lab/table-grid/create.table-grid.tsx +183 -0
- package/src/lab/table-grid/demo.tsx +53 -0
- package/src/lab/table-grid/dino.tsx +8 -0
- package/src/lab/table-grid/helpers.tsx +11 -0
- package/src/lab/table-grid/index.ts +3 -0
- package/src/lab/table-grid/item-actions.tsx +138 -0
- package/src/lab/table-grid/toolbar-pannel.tsx +98 -0
- package/src/lab/table-grid/types.ts +68 -0
- package/src/redux/create.hoc-lazy.tsx +80 -0
- package/src/redux/dino.ts +9 -0
- package/src/redux/index.ts +6 -0
- package/src/redux/types.ts +27 -0
- package/src/redux/ui.error-page.tsx +62 -0
- package/src/redux/ui.units.tsx +41 -0
- package/src/redux/vector-404.webp +0 -0
- package/src/table/context.tsx +16 -0
- package/src/table/create.action-row.tsx +91 -0
- package/src/table/create.status-cell.tsx +51 -0
- package/src/table/create.table.tsx +239 -0
- package/src/table/custom.filter-operators.ts +94 -0
- package/src/table/dino.tsx +120 -0
- package/src/table/helpers.ts +94 -0
- package/src/table/index.ts +13 -0
- package/src/table/model-filter.ts +43 -0
- package/src/table/toolbar-pannel.tsx +106 -0
- package/src/table/types.ts +50 -0
- package/src/table/ui.buttons.tsx +54 -0
- package/src/table/ui.units.tsx +189 -0
- package/src/utils/dayjs-config.ts +13 -0
- package/src/utils/helpers.ts +171 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/json-object.ts +29 -0
- package/src/utils/mfe-events.tsx +34 -0
- package/src/utils/query-param.ts +129 -0
- 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,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
|
+
}
|