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,194 @@
|
|
|
1
|
+
import React, { Component, ReactNode } from 'react'
|
|
2
|
+
import { Box, styled, Typography } from '@mui/material'
|
|
3
|
+
import { DataGrid, DataGridProps, GridColDef, GridFeatureMode, GridPaginationModel, GridRowSelectionModel } from '@mui/x-data-grid'
|
|
4
|
+
import { SelectModel } from './types'
|
|
5
|
+
|
|
6
|
+
const tableCustomClasses = {
|
|
7
|
+
root: 'TableCustom-root',
|
|
8
|
+
content: 'TableCustom-content',
|
|
9
|
+
header: 'TableCustom-header',
|
|
10
|
+
contentInner: 'TableCustom-contentInner'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface IProps {
|
|
14
|
+
title: string
|
|
15
|
+
rows: SelectModel[]
|
|
16
|
+
featureMode?: GridFeatureMode
|
|
17
|
+
totalRows?: number
|
|
18
|
+
maxSelected?: number
|
|
19
|
+
pagination?: GridPaginationModel
|
|
20
|
+
defaultPagination?: GridPaginationModel
|
|
21
|
+
loading?: boolean
|
|
22
|
+
onPaginationChange?: DataGridProps['onPaginationModelChange']
|
|
23
|
+
isRowSelectable?: DataGridProps['isRowSelectable']
|
|
24
|
+
slots?: {
|
|
25
|
+
title?: { before?: ReactNode; after?: ReactNode }
|
|
26
|
+
renderAction?: (value: SelectModel) => ReactNode
|
|
27
|
+
renderHeaderAction?: (parmas: { rowSelecteds: GridRowSelectionModel }) => ReactNode
|
|
28
|
+
header?: ReactNode
|
|
29
|
+
topBar?: ReactNode
|
|
30
|
+
dataGridProps?: Partial<DataGridProps>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TableCustom extends Component<IProps> {
|
|
35
|
+
rowSelecteds: GridRowSelectionModel
|
|
36
|
+
constructor(props: IProps) {
|
|
37
|
+
super(props)
|
|
38
|
+
this.rowSelecteds = []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
shouldComponentUpdate(nextProps: Readonly<IProps>, nextState: Readonly<{}>, nextContext: any): boolean {
|
|
42
|
+
if (this.props.rows.length !== nextProps.rows.length) this.rowSelecteds = []
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render() {
|
|
47
|
+
return (
|
|
48
|
+
<Wrap className={tableCustomClasses.root}>
|
|
49
|
+
<Box className={tableCustomClasses.header}>
|
|
50
|
+
{this.props.slots?.title?.before}
|
|
51
|
+
<Typography variant='subtitle1' gutterBottom sx={{ fontWeight: 700, flex: 1 }}>
|
|
52
|
+
{this.props.title}
|
|
53
|
+
</Typography>
|
|
54
|
+
{this.props.slots?.title?.after}
|
|
55
|
+
</Box>
|
|
56
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px', height: '48px', mb: '4px' }}>{this.props.slots?.topBar}</Box>
|
|
57
|
+
<div className={tableCustomClasses.content}>
|
|
58
|
+
<div className={tableCustomClasses.contentInner}>
|
|
59
|
+
<DataGridCustom {...this.getDataGridProps()} {...this.props.slots?.dataGridProps} />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</Wrap>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getDataGridProps = (): DataGridProps => {
|
|
67
|
+
const columns: GridColDef[] = this.getColumns()
|
|
68
|
+
const data = this.getData()
|
|
69
|
+
let obj: DataGridProps = {
|
|
70
|
+
rows: data.rows,
|
|
71
|
+
columns: columns,
|
|
72
|
+
getRowId: (x) => x.value,
|
|
73
|
+
initialState: { pagination: { paginationModel: this.props.defaultPagination } },
|
|
74
|
+
rowSelectionModel: data.rowSelecteds,
|
|
75
|
+
onRowSelectionModelChange: this.handleRowSelectionChange,
|
|
76
|
+
isRowSelectable: (x) => !x.row.disabled,
|
|
77
|
+
getRowClassName: (x) => (x.row.disabled ? 'Mui-disabled-row' : ''),
|
|
78
|
+
checkboxSelection: true
|
|
79
|
+
}
|
|
80
|
+
if (this.props.featureMode === 'server') {
|
|
81
|
+
obj = {
|
|
82
|
+
...obj,
|
|
83
|
+
paginationMode: this.props.featureMode,
|
|
84
|
+
loading: this.props.loading,
|
|
85
|
+
rowCount: this.props.totalRows,
|
|
86
|
+
paginationModel: this.props.pagination,
|
|
87
|
+
onPaginationModelChange: this.props.onPaginationChange
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return obj
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getData = () => {
|
|
94
|
+
let list = [...this.props.rows]
|
|
95
|
+
const rowSelecteds = this.rowSelecteds.filter((x) => list.some((i) => i.value === x.toString() && !i.disabled))
|
|
96
|
+
|
|
97
|
+
if (this.props.maxSelected && rowSelecteds.length >= this.props.maxSelected) {
|
|
98
|
+
const selectedSet = new Set(rowSelecteds)
|
|
99
|
+
list = this.props.rows.map((x) => (selectedSet.has(x.value) ? x : { ...x, disabled: true }))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { rows: list, rowSelecteds }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getColumns = (): GridColDef[] => {
|
|
106
|
+
const { slots } = this.props
|
|
107
|
+
const data = this.getData()
|
|
108
|
+
return [
|
|
109
|
+
{
|
|
110
|
+
field: 'value',
|
|
111
|
+
headerName: 'Medias',
|
|
112
|
+
sortable: false,
|
|
113
|
+
disableColumnMenu: true,
|
|
114
|
+
filterable: false,
|
|
115
|
+
flex: 1,
|
|
116
|
+
renderHeader: () => (
|
|
117
|
+
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', pr: '2px' }}>
|
|
118
|
+
{this.renderHeader()}
|
|
119
|
+
{slots?.renderHeaderAction && slots.renderHeaderAction({ rowSelecteds: data.rowSelecteds })}
|
|
120
|
+
</Box>
|
|
121
|
+
),
|
|
122
|
+
renderCell: (params: { row: SelectModel }) => (
|
|
123
|
+
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
|
124
|
+
<Typography variant='body2' noWrap sx={{ flex: 1 }}>
|
|
125
|
+
{params.row.label ?? params.row.value}
|
|
126
|
+
</Typography>
|
|
127
|
+
{this.props.slots?.renderAction && this.props.slots.renderAction(params.row)}
|
|
128
|
+
</Box>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
renderHeader = () => {
|
|
135
|
+
const { slots } = this.props
|
|
136
|
+
if (!slots?.header || typeof slots?.header === 'string') {
|
|
137
|
+
return (
|
|
138
|
+
<Typography variant='body2' noWrap sx={{ flex: 1, fontWeight: 700 }}>
|
|
139
|
+
{slots?.header ?? 'Media'}
|
|
140
|
+
</Typography>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
return slots?.header
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleRowSelectionChange = (value: GridRowSelectionModel) => {
|
|
147
|
+
if (this.props.maxSelected && value.length > this.props.maxSelected) {
|
|
148
|
+
this.rowSelecteds = value.slice(0, this.props.maxSelected)
|
|
149
|
+
} else {
|
|
150
|
+
this.rowSelecteds = value
|
|
151
|
+
}
|
|
152
|
+
this.forceUpdate()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const DataGridCustom = styled(DataGrid)({
|
|
157
|
+
border: 'none',
|
|
158
|
+
'.MuiDataGrid-columnHeaderTitleContainerContent': {
|
|
159
|
+
width: '100%'
|
|
160
|
+
},
|
|
161
|
+
'.Mui-disabled-row': {
|
|
162
|
+
pointerEvents: 'none',
|
|
163
|
+
opacity: 0.4
|
|
164
|
+
},
|
|
165
|
+
'& .MuiDataGrid-cell': {
|
|
166
|
+
borderBottom: 'none'
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const Wrap = styled(Box)({
|
|
171
|
+
flex: 1,
|
|
172
|
+
paddingTop: '10px',
|
|
173
|
+
height: '100%',
|
|
174
|
+
display: 'flex',
|
|
175
|
+
flexDirection: 'column',
|
|
176
|
+
minHeight: '300px',
|
|
177
|
+
[`.${tableCustomClasses.header}`]: {
|
|
178
|
+
display: 'flex',
|
|
179
|
+
alignItems: 'center'
|
|
180
|
+
},
|
|
181
|
+
[`.${tableCustomClasses.content}`]: {
|
|
182
|
+
flex: 1,
|
|
183
|
+
position: 'relative'
|
|
184
|
+
},
|
|
185
|
+
[`.${tableCustomClasses.contentInner}`]: {
|
|
186
|
+
position: 'absolute',
|
|
187
|
+
top: 0,
|
|
188
|
+
left: 0,
|
|
189
|
+
width: '100%',
|
|
190
|
+
height: '100%'
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const Custom = styled(Box)(({ theme }) => ({}))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FilterState } from '../filter-bar'
|
|
2
|
+
|
|
3
|
+
export interface SelectModel<F = any> {
|
|
4
|
+
value: string
|
|
5
|
+
label?: string
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
other?: F
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PaginationModel {
|
|
11
|
+
/** @default 0 */
|
|
12
|
+
page: number
|
|
13
|
+
/** @default 100 */
|
|
14
|
+
pageSize: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FilterStateCustom<T> extends FilterState<T> {
|
|
18
|
+
pagination: PaginationModel
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SeclectMulFetchData<T> = (
|
|
22
|
+
filterState: FilterStateCustom<T>,
|
|
23
|
+
signal?: AbortSignal
|
|
24
|
+
) => Promise<{ items: SelectModel<T>[]; totalItems: number }>
|
|
25
|
+
|
|
26
|
+
export type SeclectMulFetchTotal = (signal?: AbortSignal) => Promise<number>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React, { ChangeEvent, FC, useState } from 'react'
|
|
2
|
+
import { IconButton, InputAdornment, InputBase, InputBaseProps } from '@mui/material'
|
|
3
|
+
import { Box, Button, ButtonProps, FormControlLabel, styled, Switch, SwitchProps, Typography } from '@mui/material'
|
|
4
|
+
import EastIcon from '@mui/icons-material/East'
|
|
5
|
+
import ClearIcon from '@mui/icons-material/Clear'
|
|
6
|
+
import CheckIcon from '@mui/icons-material/Check'
|
|
7
|
+
import RemoveIcon from '@mui/icons-material/Remove'
|
|
8
|
+
import SearchIcon from '@mui/icons-material/Search'
|
|
9
|
+
import PlaylistRemoveIcon from '@mui/icons-material/PlaylistRemove'
|
|
10
|
+
import { SelectModel } from './types'
|
|
11
|
+
import { LoadingButton, LoadingButtonProps } from '../../components/loading-buttons'
|
|
12
|
+
import HelpTooltip from '../../components/help-tooltip'
|
|
13
|
+
|
|
14
|
+
export const ButtonAddToRight: FC<ButtonProps> = (props) => (
|
|
15
|
+
<Button size='small' endIcon={<EastIcon fontSize='small' />} sx={{ whiteSpace: 'nowrap', flex: '0 0 auto', textTransform: 'none' }} {...props}>
|
|
16
|
+
Add to Right
|
|
17
|
+
</Button>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export const ButtonRemoveSelected: FC<ButtonProps> = (props) => (
|
|
21
|
+
<Button
|
|
22
|
+
size='small'
|
|
23
|
+
endIcon={<PlaylistRemoveIcon fontSize='small' />}
|
|
24
|
+
sx={{ whiteSpace: 'nowrap', flex: '0 0 auto', textTransform: 'none' }}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
Remove Selected
|
|
28
|
+
</Button>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const ButtonAdd: FC<Omit<ButtonProps, 'value'> & { value: SelectModel }> = ({ value, ...props }) => {
|
|
32
|
+
if (value.disabled) {
|
|
33
|
+
return (
|
|
34
|
+
<Box sx={{ padding: '5px' }}>
|
|
35
|
+
<CheckIcon fontSize='small' />
|
|
36
|
+
</Box>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
<IconButton size='small' {...props}>
|
|
41
|
+
<EastIcon fontSize='small' />
|
|
42
|
+
</IconButton>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const ButtonRemove: FC<ButtonProps> = (props) => (
|
|
47
|
+
<IconButton size='small' {...props}>
|
|
48
|
+
<RemoveIcon fontSize='small' />
|
|
49
|
+
</IconButton>
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
export const ButtonSubmit: FC<LoadingButtonProps> = (props) => (
|
|
53
|
+
<LoadingButton size='small' variant='contained' sx={{ minWidth: '120px' }} {...props}>
|
|
54
|
+
Submit
|
|
55
|
+
</LoadingButton>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const Buttons = {
|
|
59
|
+
AddToRight: ButtonAddToRight,
|
|
60
|
+
RemoveSelected: ButtonRemoveSelected,
|
|
61
|
+
Add: ButtonAdd,
|
|
62
|
+
Remove: ButtonRemove,
|
|
63
|
+
Submit: ButtonSubmit
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// export const FooterBar: FC<ButtonProps> = (props) => (
|
|
67
|
+
// <Box sx={{ display: 'flex', justifyContent: 'flex-end', padding: '5px 10px 5px' }}>
|
|
68
|
+
// <Button size='small' variant='contained' disabled={props.disabled} onClick={props.onClick}>
|
|
69
|
+
// Confirm Selection
|
|
70
|
+
// </Button>
|
|
71
|
+
// </Box>
|
|
72
|
+
// )
|
|
73
|
+
|
|
74
|
+
export const SwitchUnselected: FC<SwitchProps & { totalHidden?: number }> = ({ totalHidden: hiddenCount, ...props }) => (
|
|
75
|
+
<FormControlLabel
|
|
76
|
+
control={<Switch size='small' {...props} />}
|
|
77
|
+
label={
|
|
78
|
+
<Typography variant='body2'>
|
|
79
|
+
only unselected <Typography variant='caption'> (hidden: {hiddenCount ?? 0})</Typography>
|
|
80
|
+
</Typography>
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
interface InputSearchLocalProps extends Omit<InputBaseProps, 'onChange'> {
|
|
86
|
+
onChange?: (value: string) => void
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const InputSearchLocal: FC<InputSearchLocalProps> = ({ onChange, ...props }) => {
|
|
90
|
+
const [value, setValue] = useState('')
|
|
91
|
+
|
|
92
|
+
const searchIncludes = ['Media name']
|
|
93
|
+
|
|
94
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
95
|
+
setValue(event.target.value)
|
|
96
|
+
onChange && onChange(event.target.value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const handleClear = () => {
|
|
100
|
+
setValue('')
|
|
101
|
+
onChange && onChange('')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<InputBaseCustom
|
|
106
|
+
placeholder='Find in selected list'
|
|
107
|
+
fullWidth
|
|
108
|
+
value={value}
|
|
109
|
+
onChange={handleChange}
|
|
110
|
+
startAdornment={
|
|
111
|
+
<InputAdornment position='start'>
|
|
112
|
+
<SearchIcon />
|
|
113
|
+
</InputAdornment>
|
|
114
|
+
}
|
|
115
|
+
endAdornment={
|
|
116
|
+
<InputAdornment position='end'>
|
|
117
|
+
{value && (
|
|
118
|
+
<IconButton size='small' onClick={handleClear}>
|
|
119
|
+
<ClearIcon fontSize='small' />
|
|
120
|
+
</IconButton>
|
|
121
|
+
)}
|
|
122
|
+
<HelpTooltip small title='The search includes'>
|
|
123
|
+
<WrapList>
|
|
124
|
+
{searchIncludes.map((item, index) => (
|
|
125
|
+
<Typography key={index} component='li' variant='body2'>
|
|
126
|
+
{item}
|
|
127
|
+
</Typography>
|
|
128
|
+
))}
|
|
129
|
+
</WrapList>
|
|
130
|
+
</HelpTooltip>
|
|
131
|
+
</InputAdornment>
|
|
132
|
+
}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const InputBaseCustom = styled(InputBase)({
|
|
139
|
+
height: '44px',
|
|
140
|
+
borderRadius: '6px',
|
|
141
|
+
padding: '0 10px',
|
|
142
|
+
transition: 'all linear 0.2s',
|
|
143
|
+
backgroundColor: '#fafafa',
|
|
144
|
+
'&:hover': { backgroundColor: '#ededed' }
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const WrapList = styled('ul')({
|
|
148
|
+
paddingLeft: '1.7rem',
|
|
149
|
+
marginBottom: 0,
|
|
150
|
+
li: {
|
|
151
|
+
position: 'relative',
|
|
152
|
+
textAlign: 'justify',
|
|
153
|
+
'&::before': {
|
|
154
|
+
content: '"►"',
|
|
155
|
+
display: 'block',
|
|
156
|
+
position: 'absolute',
|
|
157
|
+
top: '50%',
|
|
158
|
+
right: 'calc(100% + 6px)',
|
|
159
|
+
transform: 'translateY(-50%)',
|
|
160
|
+
fontSize: '0.9em'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
import { EFieldValidate, ESearchMatch, FilterField, FilterFields, FilterItemModel, FilterModel, FilterState } from './types'
|
|
3
|
+
|
|
4
|
+
export const filterPanelClasses = {
|
|
5
|
+
root: 'FilterPanel-root',
|
|
6
|
+
list: 'FilterPanel-list',
|
|
7
|
+
item: 'FilterPanel-item'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default class FilterBarBase<P, S, T = any> extends Component<P, S> {
|
|
11
|
+
//#region Filter
|
|
12
|
+
mergeFilter = (f1?: FilterState<T>, f2?: FilterState<T>): FilterState<T> => {
|
|
13
|
+
const mergedFilter: FilterModel<T> = f2?.filter ?? {}
|
|
14
|
+
|
|
15
|
+
if (f1?.filter) {
|
|
16
|
+
for (const key in f1?.filter) {
|
|
17
|
+
const fieldKey = key as keyof T
|
|
18
|
+
const existingItems = mergedFilter[fieldKey] ?? []
|
|
19
|
+
const newItems = f1?.filter[fieldKey] ?? []
|
|
20
|
+
|
|
21
|
+
const existingValues = new Set(existingItems.map((item) => item.value))
|
|
22
|
+
|
|
23
|
+
const mergedItems = [...existingItems, ...newItems.filter((item) => !existingValues.has(item.value))]
|
|
24
|
+
|
|
25
|
+
if (mergedItems.length > 0) {
|
|
26
|
+
mergedFilter[fieldKey] = mergedItems
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result: FilterState<T> = {
|
|
32
|
+
filter: Object.keys(mergedFilter).length ? mergedFilter : {},
|
|
33
|
+
details: f2?.details ?? f1?.details
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
patchFilterWithKey = (filterState: FilterState<T>, key: keyof FilterModel<T>, value: FilterItemModel) => {
|
|
40
|
+
const clonedFilter: FilterModel<T> = { ...(filterState.filter || {}) }
|
|
41
|
+
let filter = clonedFilter[key] ?? []
|
|
42
|
+
const isChanged = !filter.map((x) => x.value).includes(value.value)
|
|
43
|
+
if (isChanged) filter = [...filter, value]
|
|
44
|
+
clonedFilter[key] = filter
|
|
45
|
+
return { isChanged, filterState: { ...filterState, filter: clonedFilter } }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteFilterWithKey = (filterState: FilterState<T>, key: keyof FilterModel<T>) => {
|
|
49
|
+
const clonedFilter: FilterModel<T> = { ...(filterState.filter || {}) }
|
|
50
|
+
const isChanged = key in clonedFilter
|
|
51
|
+
if (isChanged) delete clonedFilter[key]
|
|
52
|
+
return { isChanged, filterState: { ...filterState, filter: clonedFilter } }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isFilterEmpty = (filterState: FilterState<T>) => {
|
|
56
|
+
const values = Object.values(filterState.filter ?? {})
|
|
57
|
+
return values.length < 1 || values.every((x) => x.length < 1)
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
|
|
61
|
+
//#region Field
|
|
62
|
+
isYoutubeLink = (val: string) => /^https?:\/\/(www\.)?(youtube\.com|youtu\.be)\//.test(val)
|
|
63
|
+
|
|
64
|
+
isGenericLink = (val: string) => /^https?:\/\/[^ ]+$/.test(val)
|
|
65
|
+
|
|
66
|
+
isGuid = (val: string) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(val)
|
|
67
|
+
|
|
68
|
+
getFieldsMatches = (fields: FilterFields<T>, keyword: string, showAll?: boolean): FilterFields<T> => {
|
|
69
|
+
const trimmed = keyword.trim().toLowerCase()
|
|
70
|
+
if (!trimmed) return fields
|
|
71
|
+
|
|
72
|
+
const matched: FilterFields<T> = {}
|
|
73
|
+
|
|
74
|
+
for (const key in fields) {
|
|
75
|
+
const field = key as keyof FilterFields<T>
|
|
76
|
+
const config = fields[field]
|
|
77
|
+
if (!config) continue
|
|
78
|
+
|
|
79
|
+
const matches = Array.isArray(config.searchMatches) ? config.searchMatches : config.searchMatches ? [config.searchMatches] : []
|
|
80
|
+
|
|
81
|
+
let visible = false
|
|
82
|
+
|
|
83
|
+
for (const matchObj of matches) {
|
|
84
|
+
switch (matchObj.rule) {
|
|
85
|
+
case ESearchMatch.AlwaysVisible:
|
|
86
|
+
visible = true
|
|
87
|
+
break
|
|
88
|
+
case ESearchMatch.MatchOnly:
|
|
89
|
+
if (typeof matchObj.match === 'function') {
|
|
90
|
+
if (matchObj.match(trimmed)) visible = true
|
|
91
|
+
}
|
|
92
|
+
break
|
|
93
|
+
case ESearchMatch.LinkYoutube:
|
|
94
|
+
if (this.isYoutubeLink(trimmed)) visible = true
|
|
95
|
+
break
|
|
96
|
+
case ESearchMatch.Link:
|
|
97
|
+
if (this.isGenericLink(trimmed)) visible = true
|
|
98
|
+
break
|
|
99
|
+
case ESearchMatch.Guid:
|
|
100
|
+
if (this.isGuid(trimmed)) visible = true
|
|
101
|
+
break
|
|
102
|
+
default:
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (visible) break
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (visible) matched[field] = config
|
|
110
|
+
}
|
|
111
|
+
return Object.keys(matched).length === 0 ? (showAll ? fields : {}) : matched
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
validated = (value: string, fieldKey: keyof FilterModel<T>, filterField?: FilterField): { error: boolean; message?: string } => {
|
|
115
|
+
const trimmed = value.trim().toLowerCase()
|
|
116
|
+
if (!trimmed) return { error: true, message: `The ${fieldKey.toString()} is required ` }
|
|
117
|
+
const validate = Array.isArray(filterField?.validate) ? (filterField?.validate ?? []) : filterField?.validate ? [filterField.validate] : []
|
|
118
|
+
const obj: { error: boolean; message?: string } = { error: false }
|
|
119
|
+
for (const validateObj of validate) {
|
|
120
|
+
switch (validateObj.rule) {
|
|
121
|
+
case EFieldValidate.LinkYoutube:
|
|
122
|
+
if (!this.isYoutubeLink(trimmed)) {
|
|
123
|
+
obj.error = true
|
|
124
|
+
obj.message = `The ${filterField?.label ?? fieldKey.toString()} must be a valid YouTube link`
|
|
125
|
+
}
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
case EFieldValidate.Link:
|
|
129
|
+
if (!this.isGenericLink(trimmed)) {
|
|
130
|
+
obj.error = true
|
|
131
|
+
obj.message = `The ${filterField?.label ?? fieldKey.toString()} must be a valid URL link`
|
|
132
|
+
}
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
case EFieldValidate.Custom:
|
|
136
|
+
if (typeof validateObj.custom === 'function' && !validateObj.custom(trimmed)) {
|
|
137
|
+
obj.error = true
|
|
138
|
+
obj.message = `The ${filterField?.label ?? fieldKey.toString()} is invalid`
|
|
139
|
+
}
|
|
140
|
+
break
|
|
141
|
+
default:
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
if (obj.error) break
|
|
145
|
+
}
|
|
146
|
+
return obj
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
|
|
150
|
+
//#region Sort
|
|
151
|
+
getSortFields = (fields: FilterFields<T>) => {
|
|
152
|
+
const keys = Object.keys(fields) as (keyof FilterFields<T>)[]
|
|
153
|
+
return keys.reduce<FilterFields<T>>((a, b) => {
|
|
154
|
+
const item = fields[b]
|
|
155
|
+
if (item?.sortable !== false) {
|
|
156
|
+
a[b] = item
|
|
157
|
+
}
|
|
158
|
+
return a
|
|
159
|
+
}, {})
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
}
|