From fc3a48a36723d45422c2a7cf3a3732704eb64233 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Thu, 20 Aug 2026 16:20:06 +0530 Subject: [PATCH] refactor: reuse FkMultiSelectField for report period/payee selectors Export FkMultiSelectField from react-openapi and use it directly in the report viewer for the Period/Payee dimension filters instead of the custom OptionMultiSelect component, gaining the admin panel's QoL behavior (no close-on-click, selected-first with tick marks, chips). Feed it fabricated FieldConfigs plus fkOptions derived from the report's metadata.group_options; delete the standalone OptionMultiSelect. --- react-openapi/index.ts | 1 + src/Reports/ReportViewer.tsx | 69 ++++++++++++++++----- src/common/components/OptionMultiSelect.tsx | 55 ---------------- 3 files changed, 56 insertions(+), 69 deletions(-) delete mode 100644 src/common/components/OptionMultiSelect.tsx diff --git a/react-openapi/index.ts b/react-openapi/index.ts index 0f1a3ac..963d1f2 100644 --- a/react-openapi/index.ts +++ b/react-openapi/index.ts @@ -6,6 +6,7 @@ export { useResource } from "./src/context/useResource"; export { ListCellRenderer, DetailFieldRenderer, applyDisplayFormat } from "./src/components/fields"; export { FormFieldRenderer } from "./src/components/fields/FormFieldRenderer"; export { CurrencyField, formatCurrency } from "./src/components/fields/renderers/CurrencyField"; +export { FkMultiSelectField } from "./src/components/fields/renderers/FkMultiSelectField"; export { SseStreamView } from "./src/components/SseStreamView"; export { SseConnectionStatus } from "./src/components/SseConnectionStatus"; export { getApi } from "./src/hooks/useApi"; diff --git a/src/Reports/ReportViewer.tsx b/src/Reports/ReportViewer.tsx index f01f51e..3a291cf 100644 --- a/src/Reports/ReportViewer.tsx +++ b/src/Reports/ReportViewer.tsx @@ -10,14 +10,42 @@ import { } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import CachedIcon from "@mui/icons-material/Cached"; -import { useResource, formatCurrency } from "../../react-openapi"; +import { FkMultiSelectField, useResource, formatCurrency } from "../../react-openapi"; +import type { FieldConfig } from "../../react-openapi"; import { StatCard } from "../common/components/StatCard"; import { TransactionList } from "../common/components/TransactionList"; -import { OptionMultiSelect } from "../common/components/OptionMultiSelect"; import type { TxnFieldConfigs } from "../common/types"; import { toPeriodGranularity } from "../common/utils/transactions"; import { aggregateSlice } from "./types"; +const periodField: FieldConfig = { + name: "period", + label: "Period", + description: "", + type: "string", + order: 0, + hidden: {}, + filterable: true, + sortable: false, + readOnly: false, + required: false, + isArray: true, +}; + +const payeeField: FieldConfig = { + name: "payee", + label: "Payee", + description: "", + type: "string", + order: 0, + hidden: {}, + filterable: true, + sortable: false, + readOnly: false, + required: false, + isArray: true, +}; + interface ReportViewerProps { id: string; version: number; @@ -70,6 +98,15 @@ export function ReportViewer({ id, version, fields, onClose, onRegenerated }: Re }; }, [report]); + const periodOptions = useMemo( + () => groupOptions.period.map((label: string) => ({ value: label, label })), + [groupOptions], + ); + const payeeOptions = useMemo( + () => groupOptions.payee.map((label: string) => ({ value: label, label })), + [groupOptions], + ); + const slice = useMemo( () => aggregateSlice(data ?? [], { periods: selectedPeriods, payees: selectedPayees }), [data, selectedPeriods, selectedPayees], @@ -149,18 +186,22 @@ export function ReportViewer({ id, version, fields, onClose, onRegenerated }: Re ) : ( - - + + + + + + Showing {slice.count} transactions across {slice.txns.length} rows diff --git a/src/common/components/OptionMultiSelect.tsx b/src/common/components/OptionMultiSelect.tsx deleted file mode 100644 index 0a4d23b..0000000 --- a/src/common/components/OptionMultiSelect.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useMemo } from "react"; -import { Autocomplete, TextField, Chip, Box } from "@mui/material"; -import DoneIcon from "@mui/icons-material/Done"; - -interface OptionMultiSelectProps { - label: string; - options: string[]; - value: string[]; - onChange: (value: string[]) => void; -} - -export function OptionMultiSelect({ label, options, value, onChange }: OptionMultiSelectProps) { - const sortedOptions = useMemo(() => { - const sel = new Set(value); - const picked: string[] = []; - const rest: string[] = []; - for (const opt of options) { - (sel.has(opt) ? picked : rest).push(opt); - } - return [...picked, ...rest]; - }, [options, value]); - - return ( - onChange(newVal)} - disabled={options.length === 0} - renderOption={(props, option, { selected }) => ( -
  • - {selected ? : } - {option} -
  • - )} - renderTags={(tagValue, getTagProps) => { - const maxChips = 1; - return ( - <> - {tagValue.slice(0, maxChips).map((tag, index) => { - const { key, ...tagProps } = getTagProps({ index }); - return 12 ? `${tag.slice(0, 10)}..` : tag} size="small" />; - })} - {tagValue.length > maxChips && } - - ); - }} - renderInput={(params) => ( - - )} - /> - ); -} \ No newline at end of file