Skip to content

Commit 3143280

Browse files
authored
add cf date token (#1087)
1 parent 8bb4e76 commit 3143280

File tree

9 files changed

+256
-11
lines changed

9 files changed

+256
-11
lines changed

frontend/apps/ui/src/features/search/components/AutocompleteOptions.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {useGetDocumentTypesQuery} from "@/features/document-types/storage/api"
55
import {selectDocumentCategoryID} from "@/features/documentsList/storage/documentsByCategory"
66
import {Category} from "@/features/documentsList/types"
77
import {
8+
SearchCalendarDateSuggestion,
89
SearchCategorySuggestion,
910
SearchCustomFieldSuggestion,
1011
SearchFilterSuggestion,
@@ -16,9 +17,10 @@ import {hasThisTypeSuggestion} from "@/features/search/microcomp/utils"
1617
import {useGetTagsQuery} from "@/features/tags/storage/api"
1718
import {ColoredTag, CustomField} from "@/types"
1819
import {Combobox, Loader} from "@mantine/core"
20+
import {DatePicker} from "@mantine/dates"
1921
import {skipToken} from "@reduxjs/toolkit/query"
2022
import {TFunction} from "i18next"
21-
import {ReactNode} from "react"
23+
import {ReactNode, useState} from "react"
2224

2325
interface Args {
2426
suggestions?: SearchSuggestion[]
@@ -102,6 +104,12 @@ export default function AutocompleteOptions({suggestions}: Args) {
102104
/>
103105
)
104106
}
107+
108+
if (suggestion.type == "calendarDate") {
109+
components.push(
110+
<CalendarDate key={suggestion.type} suggestion={suggestion} />
111+
)
112+
}
105113
}
106114

107115
return (
@@ -233,3 +241,27 @@ function AutocompleCustomFieldOptions({
233241
</Combobox.Group>
234242
)
235243
}
244+
245+
interface CalendarDateArgs {
246+
suggestion: SearchCalendarDateSuggestion
247+
}
248+
249+
function CalendarDate({suggestion}: CalendarDateArgs) {
250+
const [value, setValue] = useState<string>()
251+
252+
const handleChange = (val: string | null) => {
253+
const newValue = val || ""
254+
setValue(newValue)
255+
}
256+
257+
return (
258+
<Combobox.Option
259+
key={value}
260+
value={value || ""}
261+
data-type-handler={"date"}
262+
data-suggestion-type="calendarDate"
263+
>
264+
<DatePicker value={value} onChange={handleChange} />
265+
</Combobox.Option>
266+
)
267+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {useAppDispatch, useAppSelector} from "@/app/hooks"
2+
import {
3+
CustomFieldNumericOperator,
4+
CustomFieldToken
5+
} from "@/features/search/microcomp/types"
6+
import {removeToken, updateToken} from "@/features/search/storage/search"
7+
import {CFDateTokenPresentation} from "./CFDateToken.presentation"
8+
9+
interface CFNumericTokenContainerProps {
10+
index: number
11+
}
12+
13+
export function CFDateTokenContainer({index}: CFNumericTokenContainerProps) {
14+
const dispatch = useAppDispatch()
15+
const token = useAppSelector(
16+
state => state.search.tokens[index]
17+
) as CustomFieldToken
18+
19+
// Redux handlers
20+
const handleOperatorChange = (operator: CustomFieldNumericOperator) => {
21+
dispatch(updateToken({index, updates: {operator}}))
22+
}
23+
24+
const handleValueChange = (value: string | number) => {
25+
const num = parseInt(value as string)
26+
dispatch(updateToken({index, updates: {value: num}}))
27+
}
28+
29+
const handleRemove = () => {
30+
dispatch(removeToken(index))
31+
}
32+
33+
return (
34+
<CFDateTokenPresentation
35+
item={token}
36+
onRemove={handleRemove}
37+
onValueChange={handleValueChange}
38+
onOperatorChange={handleOperatorChange}
39+
/>
40+
)
41+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.tokenContainer {
2+
display: inline-flex;
3+
align-items: center;
4+
background-color: #f0f0f0;
5+
border-radius: 0.5rem;
6+
padding: 2px 8px;
7+
border: 1px solid #d0d0d0;
8+
cursor: pointer;
9+
transition: all 0.2s ease;
10+
position: relative;
11+
}
12+
13+
.operatorSelect input {
14+
min-height: auto;
15+
padding-right: 4px;
16+
padding-left: 4px;
17+
border: none;
18+
background-color: transparent;
19+
font-size: 14px;
20+
cursor: pointer;
21+
}
22+
23+
.operatorSelect input:hover {
24+
background-color: rgba(0, 0, 0, 0.05);
25+
}
26+
27+
.operatorSelect .mantine-Select-wrapper {
28+
margin-bottom: 0;
29+
}
30+
31+
.removeButton {
32+
position: absolute;
33+
top: -6px;
34+
right: -6px;
35+
width: 16px;
36+
height: 16px;
37+
border-radius: 50%;
38+
background-color: #dc3545;
39+
color: white;
40+
display: flex;
41+
align-items: center;
42+
justify-content: center;
43+
cursor: pointer;
44+
opacity: 0;
45+
transition: opacity 0.2s ease;
46+
border: 1px solid white;
47+
padding: 0;
48+
z-index: 10; /* Ensure it's above other elements */
49+
}
50+
51+
.tokenContainer:hover .removeButton {
52+
opacity: 1;
53+
}
54+
55+
.removeButton:hover {
56+
background-color: #c82333;
57+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {OPERATOR_NUMERIC} from "@/features/search/microcomp/const"
2+
import type {CustomFieldNumericOperator as CustomFieldNumericOperatorType} from "@/features/search/microcomp/types"
3+
import {CustomFieldToken} from "@/features/search/microcomp/types"
4+
import {ActionIcon, Box, Group, Select, Text} from "@mantine/core"
5+
import {DatePickerInput} from "@mantine/dates"
6+
import {IconX} from "@tabler/icons-react"
7+
import styles from "./CFDateToken.module.css"
8+
9+
interface Args {
10+
item: CustomFieldToken
11+
onOperatorChange?: (operator: CustomFieldNumericOperatorType) => void
12+
onValueChange?: (value: string | number) => void
13+
onRemove?: () => void
14+
}
15+
16+
export function CFDateTokenPresentation({
17+
item,
18+
onOperatorChange,
19+
onValueChange,
20+
onRemove
21+
}: Args) {
22+
const handleRemoveClick = (e: React.MouseEvent) => {
23+
e.stopPropagation()
24+
onRemove?.()
25+
}
26+
27+
return (
28+
<Box className={styles.tokenContainer} onClick={e => e.stopPropagation()}>
29+
<Group gap={0}>
30+
<Text c={"blue"}>cf:</Text>
31+
<Text c={"blue"}>{item.fieldName}:</Text>
32+
<CFNumericOperator item={item} onOperatorChange={onOperatorChange} />
33+
<DatePickerInput
34+
value={new Date(item.value)}
35+
onClick={e => e.stopPropagation()}
36+
/>
37+
</Group>
38+
<ActionIcon
39+
size="xs"
40+
className={styles.removeButton}
41+
onClick={handleRemoveClick}
42+
aria-label="Remove token"
43+
>
44+
<IconX size={10} stroke={3} />
45+
</ActionIcon>
46+
</Box>
47+
)
48+
}
49+
50+
interface TokenTagOperatorArgs {
51+
item: CustomFieldToken
52+
onOperatorChange?: (operator: CustomFieldNumericOperatorType) => void
53+
}
54+
55+
function CFNumericOperator({item, onOperatorChange}: TokenTagOperatorArgs) {
56+
const handleChange = (value: string | null) => {
57+
if (value && onOperatorChange) {
58+
// Remove the trailing colon to get just the operator
59+
const operator = value.replace(":", "") as CustomFieldNumericOperatorType
60+
onOperatorChange(operator)
61+
}
62+
}
63+
64+
return (
65+
<Select
66+
value={`${item.operator || "="}:`}
67+
w={"8ch"}
68+
data={OPERATOR_NUMERIC}
69+
size="sm"
70+
onChange={handleChange}
71+
onClick={e => e.stopPropagation()}
72+
className={styles.operatorSelect}
73+
/>
74+
)
75+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {CFDateTokenContainer as default} from "./CFDateToken.container"

frontend/apps/ui/src/features/search/components/SearchTokens/CustomFieldToken.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {useAppSelector} from "@/app/hooks"
22
import {CustomFieldToken} from "@/features/search/microcomp/types"
3+
import CFDateToken from "./CFDateToken"
34
import CFNumericToken from "./CFNumericToken"
45

56
interface Args {
@@ -15,5 +16,9 @@ export default function CustomFieldTokenComponent({index}: Args) {
1516
return <CFNumericToken index={index} />
1617
}
1718

18-
return <>Unknown Custom Field Token</>
19+
if (token.typeHandler == "date") {
20+
return <CFDateToken index={index} />
21+
}
22+
23+
return <>Unknown Custom Field Token: {token.typeHandler}</>
1924
}

frontend/apps/ui/src/features/search/hooks/useTokenSearch.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type {
77
Token
88
} from "@/features/search/microcomp/types"
99
import {autocompleteText} from "@/features/search/microcomp/utils"
10-
import {CustomFieldDataType} from "@/types"
1110
import {ComboboxOptionProps, useCombobox} from "@mantine/core"
1211
import {useCallback, useRef, useState} from "react"
1312
import {useTokens} from "./useTokens"
@@ -24,6 +23,8 @@ export const useTokenSearch = ({
2423
const [inputValue, setInputValue] = useState("")
2524
/** Custom field type currently being typed */
2625
const [currentCFType, setCurrentCFType] = useState<CustomFieldType>()
26+
const [currentSuggestionType, setCurrentSuggestionType] =
27+
useState<SuggestionType>()
2728
const {tokens, addToken, updateToken, removeToken, clearTokens} = useTokens()
2829
const [hasAutocomplete, setHasAutocomplete] = useState(false)
2930
const [autocomplete, setAutocomplete] = useState<SearchSuggestion[]>()
@@ -44,19 +45,25 @@ export const useTokenSearch = ({
4445
) => {
4546
const customFieldTypeHandler = (optionProps as any)["data-type-handler"]
4647
const suggestionType = (optionProps as any)["data-suggestion-type"]
48+
if (customFieldTypeHandler) {
49+
// remember cf type
50+
setCurrentCFType(customFieldTypeHandler)
51+
}
4752

48-
let newInputValue = autocompleteText(inputValue, val)
49-
const extraData = {
50-
typeHandler: customFieldTypeHandler as CustomFieldDataType,
51-
suggestionType: suggestionType as SuggestionType
53+
if (suggestionType) {
54+
// remember suggestion type
55+
setCurrentSuggestionType(suggestionType)
56+
}
57+
58+
let extraData = {
59+
typeHandler: currentCFType || customFieldTypeHandler,
60+
suggestionType: currentSuggestionType || suggestionType
5261
}
62+
63+
let newInputValue = autocompleteText(inputValue, val)
5364
if (suggestionType == "customField") {
5465
newInputValue = newInputValue + ":"
5566
}
56-
if (customFieldTypeHandler) {
57-
// remember cf type
58-
setCurrentCFType(customFieldTypeHandler)
59-
}
6067

6168
const {
6269
hasSuggestions,

frontend/apps/ui/src/features/search/microcomp/scanner.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,27 @@ function getCustomFieldSuggestions(
409409
return [{type: "operator", items: OPERATOR_TEXT}]
410410
}
411411
} // END of parts.lenth == 3
412+
////////////////////////////////////
413+
if (parts.length == 4) {
414+
// i.e. cf:Datum:>:
415+
// ^ user is typing here
416+
if (!extra) {
417+
return []
418+
}
419+
420+
if (extra.suggestionType != "customField") {
421+
return []
422+
}
423+
424+
if (extra.typeHandler == "date") {
425+
return [
426+
{
427+
type: "calendarDate"
428+
}
429+
]
430+
}
431+
}
432+
///////////////////////////////////
412433

413434
return []
414435
}

frontend/apps/ui/src/features/search/microcomp/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export type SuggestionType =
126126
| "operator"
127127
| "category"
128128
| "customField"
129+
| "calendarDate"
129130

130131
export interface BasicSuggestion {
131132
type: SuggestionType
@@ -158,12 +159,17 @@ export interface SearchCustomFieldSuggestion extends BasicSuggestion {
158159
exclude?: string[] // already used in current token
159160
}
160161

162+
export interface SearchCalendarDateSuggestion extends BasicSuggestion {
163+
type: "calendarDate"
164+
}
165+
161166
export type SearchSuggestion =
162167
| SearchOperatorSuggestion
163168
| SearchTagSuggestion
164169
| SearchFilterSuggestion
165170
| SearchCategorySuggestion
166171
| SearchCustomFieldSuggestion
172+
| SearchCalendarDateSuggestion
167173

168174
export interface ScanResult {
169175
token?: Token

0 commit comments

Comments
 (0)