Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 123 additions & 30 deletions src/components/ListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ import {
GridFilterItem,
useGridApiContext,
useGridSelector,
GridColDef,
} from '@mui/x-data-grid'
import { settings } from '@/settings'
import React, { useMemo, useState } from 'react'
import { CanAccess, useExport, useNavigation, useResourceParams } from '@refinedev/core'
import { Box, Chip, InputBase, Stack, Tooltip, Typography } from '@mui/material'
import {
CanAccess,
useExport,
useNavigation,
useResourceParams,
} from '@refinedev/core'
import { Box, Chip, InputBase, Stack, Typography } from '@mui/material'
import SearchIcon from '@mui/icons-material/Search'


// Shows a dismissible chip for each active column filter.
function ActiveFilterChips() {
const apiRef = useGridApiContext()
Expand All @@ -38,11 +43,23 @@ function ActiveFilterChips() {
}

return (
<Stack direction="row" sx={{ gap: 0.5, flexWrap: 'wrap', alignItems: 'center', pt: 0.5, pb: 0.25 }}>
<Stack
direction="row"
sx={{
gap: 0.5,
flexWrap: 'wrap',
alignItems: 'center',
pt: 0.5,
pb: 0.25,
}}
>
{activeFilters.map((filter) => {
const column = columns[filter.field]
const fieldLabel = column?.headerName ?? filter.field
const value = filter.value != null && filter.value !== '' ? ` ${filter.operator} "${filter.value}"` : ` ${filter.operator}`
const value =
filter.value != null && filter.value !== ''
? ` ${filter.operator} "${filter.value}"`
: ` ${filter.operator}`
return (
<Chip
key={filter.id}
Expand All @@ -64,30 +81,57 @@ function ActiveFilterChips() {
// Built-in toolbar buttons are used (not custom icon buttons) so panels anchor correctly.
function ListPageToolbar() {
return (
<GridToolbarContainer sx={{ px: 1, py: 0.5, flexDirection: 'column', alignItems: 'stretch', gap: 0 }}>
<GridToolbarContainer
sx={{
px: 1,
py: 0.5,
flexDirection: 'column',
alignItems: 'stretch',
gap: 0,
}}
>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 0.5 }}>
<GridToolbarFilterButton slotProps={{ button: { 'data-testid': 'grid-toolbar-filter-button' } }} />
<GridToolbarColumnsButton slotProps={{ button: { 'data-testid': 'grid-toolbar-columns-button' } }} />
<GridToolbarDensitySelector slotProps={{ button: { 'data-testid': 'grid-toolbar-density-selector' } }} />
<GridToolbarExport slotProps={{ button: { 'data-testid': 'grid-toolbar-export' } }} />
<GridToolbarFilterButton
slotProps={{
button: { 'data-testid': 'grid-toolbar-filter-button' },
}}
/>
<GridToolbarColumnsButton
slotProps={{
button: { 'data-testid': 'grid-toolbar-columns-button' },
}}
/>
<GridToolbarDensitySelector
slotProps={{
button: { 'data-testid': 'grid-toolbar-density-selector' },
}}
/>
<GridToolbarExport
slotProps={{ button: { 'data-testid': 'grid-toolbar-export' } }}
/>
</Box>
<ActiveFilterChips />
</GridToolbarContainer>
)
}

type ListPageProps = {
title?: string | null
description?: string | null
columns: any
title?: string
description?: string
columns: GridColDef[]
dataGridProps: any
getRowId?: (row: any) => string | number
exportProps?: any
children?: any
children?: React.ReactNode
onSelectionChange?: (selectionModel: any) => void
getRowId?: (row: any) => number
isLoading?: any
isLoading?: boolean
headerButtons?: any
disableRowClick?: boolean

searchMode?: 'client' | 'server'
searchValue?: string
onSearchChange?: (value: string) => void
searchPlaceholder?: string
}

export const ListPage: React.FC<ListPageProps> = ({
Expand All @@ -102,12 +146,18 @@ export const ListPage: React.FC<ListPageProps> = ({
isLoading,
headerButtons,
disableRowClick = false,
searchMode = 'client',
searchValue,
onSearchChange,
searchPlaceholder,
}) => {
if (!exportProps) {
exportProps = { pageSize: 1000 }
}

const [quickFilter, setQuickFilter] = useState('')
const [localQuickFilter, setLocalQuickFilter] = useState('')
const quickFilter =
searchMode === 'server' ? (searchValue ?? '') : localQuickFilter

const { show } = useNavigation()
const { resource } = useResourceParams()
Expand All @@ -133,16 +183,40 @@ export const ListPage: React.FC<ListPageProps> = ({
}

const rowCount = dataGridProps.rowCount as number | undefined

const { rows: allRows, ...restDataGridProps } = dataGridProps

const getSearchableCellValue = (row: any, col: GridColDef<any>) => {
const raw = row[col.field]

if (raw == null) return ''
if (Array.isArray(raw)) return raw.map((v) => String(v)).join(', ')
if (typeof raw === 'object') return JSON.stringify(raw)
return String(raw)
}

const filteredRows = useMemo(() => {
if (searchMode === 'server') {
return allRows ?? []
}

if (!quickFilter || !allRows) return allRows ?? []
const lower = quickFilter.toLowerCase()

const needle = quickFilter.toLowerCase().trim()

return allRows.filter((row: any) =>
Object.values(row).some((val) => String(val ?? '').toLowerCase().includes(lower))
columns.some((col) =>
getSearchableCellValue(row, col).toLowerCase().includes(needle)
)
)
}, [allRows, quickFilter])
}, [allRows, quickFilter, columns, searchMode])

const handleSearchChange = (value: string) => {
if (searchMode === 'server') {
onSearchChange?.(value)
} else {
setLocalQuickFilter(value)
}
}

return (
<CanAccess>
Expand All @@ -168,7 +242,12 @@ export const ListPage: React.FC<ListPageProps> = ({
breadcrumb={<AppBreadcrumb />}
wrapperProps={{
elevation: 0,
sx: { backgroundColor: 'background.wrapper', boxShadow: 'none', borderRadius: 1, padding: 0 },
sx: {
backgroundColor: 'background.wrapper',
boxShadow: 'none',
borderRadius: 1,
padding: 0,
},
}}
headerProps={{
sx: {
Expand All @@ -191,8 +270,8 @@ export const ListPage: React.FC<ListPageProps> = ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
px: 0.5,
pb: 0.5,
px: 0,
pb: 1.5,
}}
>
<Box
Expand All @@ -205,17 +284,29 @@ export const ListPage: React.FC<ListPageProps> = ({
borderRadius: 1,
px: 1,
py: 0.25,
width: 260,
width: 400,
bgcolor: 'background.paper',
}}
>
<SearchIcon sx={{ fontSize: 16, color: 'text.secondary', flexShrink: 0 }} />
<SearchIcon
sx={{ fontSize: 16, color: 'text.secondary', flexShrink: 0 }}
/>
<InputBase
value={quickFilter}
onChange={(e) => setQuickFilter(e.target.value)}
placeholder="Filter this page..."
onChange={(e) => handleSearchChange(e.target.value)}
placeholder={
searchPlaceholder ??
(searchMode === 'server'
? 'Search all records...'
: 'Filter this page...')
}
sx={{ fontSize: 14, flex: 1 }}
inputProps={{ 'aria-label': 'Filter rows on this page' }}
inputProps={{
'aria-label':
searchMode === 'server'
? 'Search all records'
: 'Filter rows on this page',
}}
/>
</Box>
{rowCount !== undefined && rowCount > 0 && (
Expand Down Expand Up @@ -245,7 +336,9 @@ export const ListPage: React.FC<ListPageProps> = ({
? (params) => show(resource.name, params.id as string | number)
: undefined
}
loading={isLoading !== undefined ? isLoading : restDataGridProps.loading}
loading={
isLoading !== undefined ? isLoading : restDataGridProps.loading
}
columns={columns}
sx={{ cursor: disableRowClick ? 'default' : 'pointer' }}
/>
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/ocotillo/IWell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IThing } from '@/interfaces/ocotillo'
import type { IContact, IThing } from '@/interfaces/ocotillo'
import { z } from 'zod'
import { zWellPurpose } from '@/generated/zod.gen'

Expand Down Expand Up @@ -59,4 +59,6 @@ export interface IWell extends IThing {
start_date: string | null
end_date: string | null
}[]

contacts?: Partial<IContact>[] | null
}
32 changes: 23 additions & 9 deletions src/pages/ocotillo/contact/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import { settings } from '@/settings'
import { formatAppDateTime, formatPhone } from '@/utils'
import { ListPage } from '@/components'
import { useAccessCapabilities } from '@/hooks'
import {
filterConfidentialRows,
sanitizeContacts,
} from '@/utils'
import { filterConfidentialRows, sanitizeContacts } from '@/utils'

export const ContactList: React.FC = () => {
const { canViewConfidential } = useAccessCapabilities()
Expand Down Expand Up @@ -126,7 +123,13 @@ export const ContactList: React.FC = () => {
renderCell: (params) => {
const things = params.row.things ?? []
return (
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }}>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
}}
>
{things.map((thing, idx) => (
<span key={thing.id}>
{idx > 0 && ', '}
Expand All @@ -138,6 +141,7 @@ export const ContactList: React.FC = () => {
id: thing.id,
},
}}
onClick={(e) => e.stopPropagation()}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to stop the click from bubbling up to the row.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is good. There are also good patterns for making this also clickable or to have it surface the contact card inline, but that is something we can look at later

>
{thing.name}
</Link>
Expand Down Expand Up @@ -202,9 +206,15 @@ export const ContactList: React.FC = () => {
/>
{selectedContactId && (
<>
{canViewConfidential && <EmailInfoCard dataGridProps={emailDataGridProps} />}
{canViewConfidential && <PhoneInfoCard dataGridProps={phoneDataGridProps} />}
{canViewConfidential && <AddressInfoCard dataGridProps={addressDataGridProps} />}
{canViewConfidential && (
<EmailInfoCard dataGridProps={emailDataGridProps} />
)}
{canViewConfidential && (
<PhoneInfoCard dataGridProps={phoneDataGridProps} />
)}
{canViewConfidential && (
<AddressInfoCard dataGridProps={addressDataGridProps} />
)}
</>
)}
</>
Expand Down Expand Up @@ -349,7 +359,11 @@ const InfoCard = ({
}) => (
<Card sx={{ mt: 2 }}>
<IconCardHeader text={title} icon={icon} />
<DataGrid {...dataGridProps} columns={columns} rowHeight={settings.rowHeight} />
<DataGrid
{...dataGridProps}
columns={columns}
rowHeight={settings.rowHeight}
/>
</Card>
)

Expand Down
Loading
Loading