Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- support adhoc tasks
- `formsAppId` column and filter to Form Store table

## [10.1.1] - 2026-04-23

Expand Down
4 changes: 4 additions & 0 deletions src/apps/form-store-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export type FormStoreFilters = {
/** Filter results by the task group instance label */
label?: FormStoreFilter<string>
}
/** Filter results by the forms app used to submit */
formsAppId?: {
$in: (number | null)[]
}
}

export type FormStoreParameters = {
Expand Down
3 changes: 3 additions & 0 deletions src/components/formStore/FormStoreTableProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ function getParamsFromLocalStorage() {
export function FormStoreTableProvider({
form,
children,
getFormsAppLabel,
}: {
form: FormTypes.Form
children: React.ReactNode
getFormsAppLabel?: (formsAppId: number | null) => string
}) {
const history = useHistory()
const location = useLocation()
Expand Down Expand Up @@ -142,6 +144,7 @@ export function FormStoreTableProvider({
submissionIdValidationMessage,
form,
onRefresh,
getFormsAppLabel,
})

const visibleColumns = formStoreTable.getVisibleFlatColumns()
Expand Down
6 changes: 5 additions & 1 deletion src/components/formStore/OneBlinkFormStoreProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ export const FormStoreElementsContext =
export function OneBlinkFormStoreProvider({
form,
children,
getFormsAppLabel,
}: {
form: FormTypes.Form
children: React.ReactNode
getFormsAppLabel?: (formsAppId: number | null) => string
}) {
const fetchFormStoreDefinition = React.useCallback(
(abortSignal?: AbortSignal) => {
Expand Down Expand Up @@ -55,7 +57,9 @@ export function OneBlinkFormStoreProvider({
<FormStoreElementsContext.Provider
value={formStoreDefinitionState.result.formElements}
>
<FormStoreTableProvider form={form}>{children}</FormStoreTableProvider>
<FormStoreTableProvider form={form} getFormsAppLabel={getFormsAppLabel}>
{children}
</FormStoreTableProvider>
</FormStoreElementsContext.Provider>
)
}
88 changes: 88 additions & 0 deletions src/components/formStore/table/ColumnFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type Props = {

const shortDateFormat = localisationService.getDateFnsFormats().shortDate

export type FormsAppOption = {
value: number | null
label: string
}
function ColumnFilters({ filter }: Props) {
switch (filter.type) {
case 'SUBMISSION_ID': {
Expand Down Expand Up @@ -315,6 +319,24 @@ function ColumnFilters({ filter }: Props) {
/>
)
}
case 'FORMS_APP_ID': {
return (
<FormsAppIdTextField
options={filter.options}
value={filter.value?.$in}
onChange={(newValue) => {
filter.onChange(
newValue.length
? {
$in: newValue,
}
: undefined,
false,
)
}}
/>
)
}
default: {
return null
}
Expand All @@ -323,6 +345,72 @@ function ColumnFilters({ filter }: Props) {

export default React.memo(ColumnFilters)

const NO_APP_SENTINEL = '__no_app__'

function FormsAppIdTextField({
options,
value,
onChange,
}: {
options: Array<{ value: number | null; label: string }>
value: (number | null)[] | undefined
onChange: (newValue: (number | null)[]) => void
}) {
const selectedStrings = React.useMemo(
() =>
value?.map((v) => (v === null ? NO_APP_SENTINEL : v.toString())) ?? [],
[value],
)

return (
<StyledTextField
variant="outlined"
margin="dense"
size="small"
label="Filter"
select
slotProps={{
select: {
multiple: true,
renderValue: (selectedIds) => {
return options
.reduce<string[]>((selectedLabels, option) => {
const key =
option.value === null
? NO_APP_SENTINEL
: option.value.toString()
if ((selectedIds as string[]).includes(key)) {
selectedLabels.push(option.label)
}
return selectedLabels
}, [])
.join(', ')
},
},
}}
fullWidth
value={selectedStrings}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const selected = e.target.value as unknown as string[]
onChange(
selected.map((v) => (v === NO_APP_SENTINEL ? null : parseInt(v, 10))),
)
}}
>
{options.map((option) => {
const key =
option.value === null ? NO_APP_SENTINEL : option.value.toString()
return (
<MenuItem value={key} key={key}>
<Checkbox checked={selectedStrings.includes(key)} />
<ListItemText>{option.label}</ListItemText>
</MenuItem>
)
})}
</StyledTextField>
)
}

function OptionsTextField({
options,
value,
Expand Down
66 changes: 66 additions & 0 deletions src/components/formStore/table/useFormStoreTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export default function useFormStoreTable({
onChangeParameters,
onRefresh,
submissionIdValidationMessage,
getFormsAppLabel,
}: {
formStoreRecords: SubmissionTypes.FormStoreRecord[]
form: FormTypes.Form
parameters: formStoreService.FormStoreParameters
onChangeParameters: OnChangeFilters<formStoreService.FormStoreParameters>
onRefresh: () => void
submissionIdValidationMessage?: string
getFormsAppLabel?: (formsAppId: number | null) => string
}) {
// Resets parameters on form change
React.useEffect(() => {
Expand All @@ -57,6 +59,18 @@ export default function useFormStoreTable({
}, [form, onChangeParameters])

const formElements = React.useContext(FormStoreElementsContext)

const formsAppOptions = React.useMemo(() => {
if (!getFormsAppLabel) return undefined
return [
...form.formsAppIds.map((id) => ({
value: id,
label: getFormsAppLabel(id),
})),
{ value: null, label: getFormsAppLabel(null) },
]
}, [form.formsAppIds, getFormsAppLabel])

const columns = React.useMemo(() => {
return generateColumns<SubmissionTypes.FormStoreRecord>({
sorting: parameters.sorting,
Expand Down Expand Up @@ -258,6 +272,56 @@ export default function useFormStoreTable({
</>
),
},
...(formsAppOptions && getFormsAppLabel
? [
{
id: 'FORMS_APP_ID',
header: 'App',
meta: {
sorting: undefined,
filter: {
type: 'FORMS_APP_ID' as const,
options: formsAppOptions,
value: parameters.filters?.formsAppId as
| { $in: (number | null)[] }
| undefined,
onChange: (
newValue?: formStoreService.FormStoreFilter<unknown>,
shouldDebounce?: boolean,
) => {
onChangeParameters(
(currentParameters) => ({
...currentParameters,
filters: {
...currentParameters.filters,
formsAppId: newValue as
| { $in: (number | null)[] }
| undefined,
},
}),
shouldDebounce ?? false,
)
},
},
},
cell: ({
row: { original: formStoreRecord },
}: {
row: { original: SubmissionTypes.FormStoreRecord }
}) => {
const text = getFormsAppLabel(
formStoreRecord.formsAppId ?? null,
)
return (
<>
{text}
<TableCellCopyButton text={text} />
</>
)
},
},
]
: []),
{
id: 'TASK_GROUP',
header: 'Task Group',
Expand Down Expand Up @@ -432,6 +496,8 @@ export default function useFormStoreTable({
})
}, [
formElements,
formsAppOptions,
getFormsAppLabel,
onChangeParameters,
parameters,
submissionIdValidationMessage,
Expand Down
9 changes: 8 additions & 1 deletion src/types/react-table.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FormTypes } from '@oneblink/types'
import { formStoreService } from 'apps'

import { FormsAppOption } from '../components/formStore/table/ColumnFilters'
declare module '@tanstack/react-table' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ColumnMeta<TData extends RowData, TValue> {
Expand Down Expand Up @@ -69,6 +69,13 @@ declare module '@tanstack/react-table' {
}
}
}
| {
type: 'FORMS_APP_ID'
options: Array<FormsAppOption>
value?: {
$in: (number | null)[]
}
}
)
}

Expand Down
Loading