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
64 changes: 64 additions & 0 deletions src/components/Search/CommandResults.tsx
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.

Broke up the Modal Results into their respective components in the Search folder.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Box, Stack, Typography } from '@mui/material'
import { Description } from '@mui/icons-material'
import { COMMANDS } from '@/utils/searchModal'

type CommandKey = (typeof COMMANDS)[number]['key']

type CommandResultsProps = {
commands: readonly {
key: CommandKey
label: string
description: string
}[]
onSelect: (command: CommandKey) => void
}

export const CommandResults = ({
commands,
onSelect,
}: CommandResultsProps) => (
<Box sx={{ py: 1 }}>
<Stack sx={{ px: 1.5, pb: 0.5 }}>
<Typography
variant="overline"
sx={{ color: 'text.disabled', fontSize: 10, letterSpacing: 1 }}
>
Commands
</Typography>
</Stack>

{commands.map((command) => (
<Box
key={command.key}
onClick={() => onSelect(command.key)}
sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 1.5,
px: 1.5,
py: 1,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
}}
>
<Description
sx={{
fontSize: 18,
color: 'text.secondary',
flexShrink: 0,
mt: '2px',
}}
/>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography variant="body2" fontWeight={600}>
{command.label}
</Typography>
<Typography variant="caption" color="text.secondary">
{command.description}
</Typography>
</Box>
</Box>
))}
</Box>
)
168 changes: 168 additions & 0 deletions src/components/Search/DefaultResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { Box, Chip, Divider, Typography } from '@mui/material'
import { Description, Opacity, Person } from '@mui/icons-material'
import { GroupType } from '@/constants'
import { ContactResult, SearchResult, WellResult } from '@/interfaces/ocotillo'
import { highlight } from '@/utils'

const TypeIcon = ({ group }: { group: GroupType }) => {
const sx = { fontSize: 18, color: 'text.secondary', flexShrink: 0, mt: '2px' }

switch (group) {
case GroupType.Wells:
case GroupType.Springs:
return <Opacity sx={sx} />
case GroupType.Contacts:
return <Person sx={sx} />
case GroupType.Assets:
return <Description sx={sx} />
default:
return null
}
}

const buildSubtitle = (option: SearchResult): string | null => {
if (option.group === GroupType.Wells || option.group === GroupType.Springs) {
const properties = (option as WellResult).properties
const parts: string[] = []

if (properties.owner_name) parts.push(`Owner: ${properties.owner_name}`)
if (properties.county) parts.push(properties.county)
if (properties.site_name) parts.push(properties.site_name)
if (properties.thing_type) parts.push(properties.thing_type)
if (properties.well_depth)
parts.push(`${properties.well_depth.toFixed(0)} ft`)
if (properties.hole_depth) {
parts.push(`hole ${properties.hole_depth.toFixed(0)} ft`)
}
if (properties.well_purposes?.length)
parts.push(...properties.well_purposes)

return parts.length ? parts.join(' · ') : null
}

if (option.group === GroupType.Contacts) {
const properties = (option as ContactResult).properties
const parts: string[] = []

if (properties.phone?.length) parts.push(properties.phone[0])
if (properties.address?.length) parts.push(properties.address[0])

return parts.length ? parts.join(' · ') : null
}

return null
}

const RelatedThings = ({
things,
query,
}: {
things: { id: number; label: string; thing_type: string }[]
query: string
}) => {
if (!things?.length) return null

return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 0.5 }}>
{things.slice(0, 4).map((thing) => (
<Chip
key={thing.id}
size="small"
icon={<Opacity sx={{ fontSize: '12px !important' }} />}
label={highlight(thing.label, query)}
variant="outlined"
sx={{ fontSize: 11 }}
/>
))}
{things.length > 4 && (
<Chip
size="small"
label={`+${things.length - 4} more`}
variant="outlined"
sx={{ fontSize: 11 }}
/>
)}
</Box>
)
}

const ResultRow = ({
option,
query,
onClick,
}: {
option: SearchResult
query: string
onClick: () => void
}) => {
const subtitle = buildSubtitle(option)
const relatedThings =
option.group === GroupType.Contacts
? (option as ContactResult).properties.things
: option.group === GroupType.Assets
? (option as any).properties?.things
: null

return (
<Box
onClick={onClick}
sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 1.5,
px: 1.5,
py: 1,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
}}
>
<TypeIcon group={option.group} />
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ lineHeight: 1.4 }}>
{highlight(option.label, query)}
</Typography>
{subtitle && (
<Typography
variant="caption"
color="text.secondary"
sx={{ lineHeight: 1.4, display: 'block' }}
>
{subtitle}
</Typography>
)}
{relatedThings && (
<RelatedThings things={relatedThings} query={query} />
)}
</Box>
</Box>
)
}

type DefaultResultsProps = {
grouped: Map<GroupType, SearchResult[]>
query: string
onSelect: (result: SearchResult) => void
}

export const DefaultResults = ({
grouped,
query,
onSelect,
}: DefaultResultsProps) => (
<Box sx={{ py: 0.5 }}>
{Array.from(grouped.entries()).map(([group, items], groupIndex) => (
<Box key={group}>
{groupIndex > 0 && <Divider sx={{ my: 0.5 }} />}
{items.map((option, index) => (
<ResultRow
key={`${option.group}-${(option as any).properties?.id ?? index}`}
option={option}
query={query}
onClick={() => onSelect(option)}
/>
))}
</Box>
))}
</Box>
)
48 changes: 48 additions & 0 deletions src/components/Search/DocsResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Box, Typography } from '@mui/material'
import { Description } from '@mui/icons-material'
import { DocEntry } from '@/utils/docsSearch'
import { buildDocExcerpt } from '@/utils/searchModal'
import { highlight } from '@/utils'

type DocsResultsProps = {
docs: DocEntry[]
query: string
onSelect: (doc: DocEntry) => void
}

export const DocsResults = ({ docs, query, onSelect }: DocsResultsProps) => (
<Box sx={{ py: 0.5 }}>
{docs.map((doc) => (
<Box
key={doc.id}
onClick={() => onSelect(doc)}
sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 1.5,
px: 1.5,
py: 1,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
}}
>
<Description
sx={{ fontSize: 18, color: 'text.secondary', flexShrink: 0, mt: '2px' }}
/>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ lineHeight: 1.4 }}>
{highlight(doc.title, query)}
</Typography>
<Typography
variant="caption"
color="text.secondary"
sx={{ lineHeight: 1.4, display: 'block' }}
>
{highlight(buildDocExcerpt(doc, query), query)}
</Typography>
</Box>
</Box>
))}
</Box>
)
19 changes: 19 additions & 0 deletions src/components/Search/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Typography } from '@mui/material'

type EmptyStateProps = {
color?: 'error' | 'text.secondary'
message: string
}

export const EmptyState = ({
color = 'text.secondary',
message,
}: EmptyStateProps) => (
<Typography
variant="body2"
color={color}
sx={{ px: 2, py: 2, textAlign: 'center' }}
>
{message}
</Typography>
)
62 changes: 62 additions & 0 deletions src/components/Search/GameResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Box, Stack, Typography } from '@mui/material'
import { ArcadeGame, GAMES } from '@/utils/searchModal'

type GameResultsProps = {
games: typeof GAMES
term: string
onSelect: (game: ArcadeGame) => void
}

export const GameResults = ({ games, term, onSelect }: GameResultsProps) => (
<Box sx={{ py: 1 }}>
<Stack sx={{ px: 1.5, pb: 0.5 }}>
<Typography
variant="overline"
sx={{ color: 'text.disabled', fontSize: 10, letterSpacing: 1 }}
>
Games
</Typography>
</Stack>

{games.map((game) => (
<Box
key={game.key}
onClick={() => onSelect(game.key)}
sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 1.5,
px: 1.5,
py: 1,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
}}
>
<Typography variant="body2" sx={{ minWidth: 0, flex: 1 }}>
<Box component="span" fontWeight={600}>
{game.label}
</Box>
<Typography
component="span"
variant="caption"
color="text.secondary"
sx={{ ml: 1 }}
>
{game.description}
</Typography>
</Typography>
</Box>
))}

{games.length === 0 && (
<Typography
variant="body2"
color="text.secondary"
sx={{ px: 2, py: 2, textAlign: 'center' }}
>
No games found for "{term}".
</Typography>
)}
</Box>
)
Loading
Loading