Skip to content

Commit 4774874

Browse files
committed
search results refator
1 parent f9a0c95 commit 4774874

File tree

6 files changed

+256
-47
lines changed

6 files changed

+256
-47
lines changed

apps/sensenet/src/components/Icon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export const defaultContentResolvers: Array<IconResolver<GenericContent>> = [
218218
return null
219219
}
220220

221-
if (item.Name.endsWith('.xls') || item.Name.endsWith('.xlsx')) {
221+
if (item.Name.endsWith('.xls') || item.Name.endsWith('.xlsx') || item.Name.endsWith('.xlsm')) {
222222
svgPath = '/Root/System/Images/Icons/colors/xls.svg'
223223
}
224224

apps/sensenet/src/components/grid/Cols/ColumnDefs..tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,64 @@ export const getSettingsColumnDefs = (history: any): ColDef[] => [
759759
width: 90,
760760
},
761761
]
762+
763+
export const searchColumnDefs: ColDef[] = [
764+
{
765+
headerCheckboxSelection: true,
766+
checkboxSelection: true,
767+
headerCheckboxSelectionFilteredOnly: true,
768+
width: 27,
769+
minWidth: 27,
770+
cellStyle: { padding: '0px 4px' },
771+
headerClass: 'grid-checkbox-header',
772+
},
773+
{
774+
headerName: '',
775+
field: 'Icon',
776+
width: 24,
777+
minWidth: 24,
778+
cellRenderer: IconFormatter,
779+
cellStyle: { padding: 0 },
780+
},
781+
{
782+
headerName: 'Display Name',
783+
field: 'DisplayName',
784+
headerTooltip: 'Display Name',
785+
flex: 5,
786+
filter: true,
787+
sortable: true,
788+
comparator: (valueA: string, valueB: string) => {
789+
return valueA.toLowerCase().localeCompare(valueB.toLowerCase())
790+
},
791+
resizable: true,
792+
},
793+
{
794+
headerName: 'Path',
795+
field: 'Path',
796+
headerTooltip: 'Path',
797+
flex: 5,
798+
filter: true,
799+
sortable: true,
800+
resizable: true,
801+
},
802+
{
803+
headerName: 'Modified By',
804+
field: 'ModifiedBy',
805+
headerTooltip: 'Modified By',
806+
cellRenderer: UserNameFormatter,
807+
flex: 1.5,
808+
filter: true,
809+
sortable: true,
810+
resizable: true,
811+
},
812+
{
813+
headerName: 'Actions',
814+
field: 'Actions',
815+
headerTooltip: 'Actions',
816+
cellRenderer: ActionFormatter,
817+
width: 66,
818+
resizable: false,
819+
wrapText: true,
820+
autoHeight: true,
821+
},
822+
]

apps/sensenet/src/components/search/filters/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const Filters: React.FunctionComponent<FiltersProps> = ({ defaultFilterVi
1515

1616
return (
1717
<>
18-
<div style={{ display: 'flex', justifyContent: 'space-between', margin: '15px' }}>
18+
<div style={{ display: 'flex', justifyContent: 'space-between', margin: '12px' }}>
1919
<TypeFilter />
2020

2121
<Button

apps/sensenet/src/components/search/index.tsx

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ export interface SearchFilters {
2525
const useStyles = makeStyles(() => {
2626
return createStyles({
2727
contentWrapper: {
28-
paddingRight: 30,
28+
padding: '0 30px',
29+
},
30+
contentTitle: {
31+
paddingLeft: '10px',
32+
paddingTop: '4px',
33+
},
34+
paginationControls: {
35+
display: 'flex',
36+
alignItems: 'center',
37+
gap: '10px',
38+
padding: '4px',
2939
},
3040
})
3141
})
@@ -40,6 +50,8 @@ export const Search = () => {
4050
const globalClasses = useGlobalStyles()
4151
const repository = useRepository()
4252
const [searchFilters, setSearchFilters] = useState<Partial<SearchFilters>>()
53+
const [maxSearchResult, setMaxSearchResult] = useState(200)
54+
const [currentPage, setCurrentPage] = useState(1)
4355

4456
useEffect(() => {
4557
;(async () => {
@@ -85,26 +97,30 @@ export const Search = () => {
8597
return (
8698
<SearchProvider
8799
defaultTerm={queryFromUrl ? searchFilters?.term ?? '' : termFromUrl}
88-
defaultFilters={searchFilters?.filters}>
89-
<div className={clsx(globalClasses.contentWrapper, classes.contentWrapper)}>
90-
<div className={clsx(globalClasses.contentTitle, globalClasses.centeredVertical)}>
91-
<span style={{ fontSize: '20px' }}>{localization.title}</span>
92-
</div>
100+
defaultFilters={searchFilters?.filters}
101+
currentPage={currentPage}
102+
maxSearchResult={maxSearchResult}>
103+
<div className={clsx(globalClasses.centeredVertical, classes.contentTitle)}>
104+
<span style={{ fontSize: '20px' }}>{localization.title}</span>
105+
</div>
93106

94-
<SearchBar />
107+
<SearchBar />
95108

96-
<Filters
97-
defaultFilterVisibility={
98-
queryFromUrl
99-
? !!searchFilters?.filters?.path ||
100-
searchFilters?.filters?.date.name !== defaultDateFilter.name ||
101-
searchFilters?.filters?.reference.name !== defaultReferenceFilter.name
102-
: false
103-
}
104-
/>
105-
106-
<SearchResults />
107-
</div>
109+
<Filters
110+
defaultFilterVisibility={
111+
queryFromUrl
112+
? !!searchFilters?.filters?.path ||
113+
searchFilters?.filters?.date.name !== defaultDateFilter.name ||
114+
searchFilters?.filters?.reference.name !== defaultReferenceFilter.name
115+
: false
116+
}
117+
/>
118+
<SearchResults
119+
currentPage={currentPage}
120+
setCurrentPage={setCurrentPage}
121+
maxSearchResult={maxSearchResult}
122+
setMaxSearchResult={setMaxSearchResult}
123+
/>
108124
</SearchProvider>
109125
)
110126
}

apps/sensenet/src/components/search/search-results.tsx

Lines changed: 148 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import { LinearProgress, Typography } from '@material-ui/core'
1+
import {
2+
createStyles,
3+
FormControl,
4+
IconButton,
5+
LinearProgress,
6+
makeStyles,
7+
MenuItem,
8+
Select,
9+
TextField,
10+
Typography,
11+
} from '@material-ui/core'
12+
import { ChevronLeft, ChevronRight } from '@material-ui/icons'
213
import { ConstantContent, ODataFieldParameter } from '@sensenet/client-core'
314
import { GenericContent } from '@sensenet/default-content-types'
415
import {
@@ -11,48 +22,95 @@ import React, { useContext } from 'react'
1122
import { useHistory } from 'react-router-dom'
1223
import { ResponsivePersonalSettings } from '../../context'
1324
import { useSearch } from '../../context/search'
14-
import { useLocalization, useSelectionService, useSnRoute } from '../../hooks'
25+
import { useSelectionService, useSnRoute } from '../../hooks'
1526
import { getPrimaryActionUrl } from '../../services'
16-
import { ContentList } from '../content-list'
27+
import { searchColumnDefs } from '../grid/Cols/ColumnDefs.'
28+
import { Grid } from '../grid/Grid'
29+
30+
const useStyles = makeStyles(() =>
31+
createStyles({
32+
paginationControls: {
33+
display: 'flex',
34+
alignItems: 'center',
35+
justifyContent: 'space-between',
36+
flexWrap: 'wrap',
37+
padding: '0 10px',
38+
},
39+
noSpin: {
40+
'& input[type=number]::-webkit-inner-spin-button': {
41+
WebkitAppearance: 'none',
42+
margin: 0,
43+
},
44+
'& input[type=number]::-webkit-outer-spin-button': {
45+
WebkitAppearance: 'none',
46+
margin: 0,
47+
},
48+
'& input[type=number]': {
49+
MozAppearance: 'textfield',
50+
},
51+
},
52+
containerRelative: {
53+
position: 'relative',
54+
height: '100%',
55+
display: 'flex',
56+
flexDirection: 'column',
57+
},
58+
linearProgressOverlay: {
59+
position: 'absolute',
60+
top: 0,
61+
left: 0,
62+
right: 0,
63+
zIndex: 99,
64+
},
65+
}),
66+
)
1767

18-
export const SearchResults = () => {
68+
export const SearchResults = ({
69+
currentPage,
70+
setCurrentPage,
71+
maxSearchResult,
72+
setMaxSearchResult,
73+
}: {
74+
currentPage: number
75+
setCurrentPage: (val: number) => void
76+
maxSearchResult: number
77+
setMaxSearchResult: (val: number) => void
78+
}) => {
1979
const repository = useRepository()
20-
const localization = useLocalization().search
2180
const history = useHistory()
2281
const { location } = history
2382
const selectionService = useSelectionService()
2483
const uiSettings = useContext(ResponsivePersonalSettings)
2584
const snRoute = useSnRoute()
26-
85+
const classes = useStyles()
2786
const searchState = useSearch()
2887

88+
const totalPages = Math.ceil(searchState.resultCount / maxSearchResult)
89+
const startItem = (currentPage - 1) * maxSearchResult + 1
90+
const endItem = Math.min(currentPage * maxSearchResult, searchState.resultCount)
91+
92+
const handlePageInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
93+
let value = Number(e.target.value)
94+
if (isNaN(value)) return
95+
value = Math.min(Math.max(1, value), totalPages)
96+
setCurrentPage(value)
97+
}
98+
2999
return (
30-
<>
100+
<div className={classes.containerRelative}>
31101
{searchState.error ? (
32102
<Typography color="error" variant="caption" style={{ margin: '0 1rem 1rem' }}>
33103
{searchState.error}
34104
</Typography>
35105
) : null}
36106

37-
{searchState.isLoading && <LinearProgress style={{ margin: '15px 15px 0' }} />}
38-
39-
<Typography style={{ margin: '1rem' }}>
40-
{localization.resultCount(searchState.resultCount) +
41-
(searchState.resultCount > searchState.maxSearchResult
42-
? localization.onlyResultCountDisplayed(searchState.maxSearchResult)
43-
: '')}
44-
</Typography>
107+
{searchState.isLoading && <LinearProgress className={classes.linearProgressOverlay} />}
45108

46109
<CurrentContentContext.Provider value={ConstantContent.PORTAL_ROOT}>
47110
<CurrentChildrenContext.Provider value={searchState.result}>
48111
<CurrentAncestorsContext.Provider value={[]}>
49-
<ContentList
50-
style={{
51-
height: '100%',
52-
overflow: 'auto',
53-
}}
54-
fieldsToDisplay={[{ field: 'DisplayName' }, { field: 'Path' }, { field: 'ModifiedBy' }]}
55-
enableBreadcrumbs={false}
112+
<Grid
113+
colDef={searchColumnDefs}
56114
parentIdOrPath={0}
57115
onParentChange={(p) => {
58116
history.push(getPrimaryActionUrl({ content: p, repository, uiSettings, location, snRoute }))
@@ -79,6 +137,73 @@ export const SearchResults = () => {
79137
</CurrentAncestorsContext.Provider>
80138
</CurrentChildrenContext.Provider>
81139
</CurrentContentContext.Provider>
82-
</>
140+
141+
{/* Pagination Controls */}
142+
<div className={classes.paginationControls}>
143+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
144+
<Typography variant="body2" component="label" htmlFor="per-page" style={{ fontWeight: '500' }}>
145+
Items per page
146+
</Typography>
147+
<FormControl variant="outlined" size="small" style={{ minWidth: 70 }}>
148+
<Select
149+
labelId="per-page-label"
150+
id="per-page"
151+
value={maxSearchResult}
152+
onChange={(e) => {
153+
setMaxSearchResult(Number(e.target.value))
154+
setCurrentPage(1)
155+
}}>
156+
{[50, 100, 200, 500].map((value) => (
157+
<MenuItem key={value} value={value}>
158+
{value}
159+
</MenuItem>
160+
))}
161+
</Select>
162+
</FormControl>
163+
164+
<Typography variant="body2" component="span" style={{ fontWeight: '500' }}>
165+
{startItem}-{endItem} of {searchState.resultCount}
166+
</Typography>
167+
</div>
168+
169+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
170+
<IconButton
171+
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
172+
disabled={currentPage <= 1}
173+
size="small">
174+
<ChevronLeft />
175+
</IconButton>
176+
177+
<TextField
178+
type="number"
179+
inputProps={{
180+
min: 1,
181+
max: totalPages,
182+
style: { textAlign: 'center' },
183+
}}
184+
value={currentPage}
185+
onChange={handlePageInputChange}
186+
variant="outlined"
187+
size="small"
188+
className={classes.noSpin}
189+
style={{
190+
width: 50,
191+
padding: 4,
192+
borderRadius: 4,
193+
}}
194+
/>
195+
<Typography variant="body1" component="span" style={{ fontWeight: '500' }}>
196+
of {totalPages}
197+
</Typography>
198+
199+
<IconButton
200+
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
201+
disabled={currentPage >= totalPages}
202+
size="small">
203+
<ChevronRight />
204+
</IconButton>
205+
</div>
206+
</div>
207+
</div>
83208
)
84209
}

0 commit comments

Comments
 (0)