import { flow, getParent, getRoot, types } from "mobx-state-tree"; import * as CellViews from "../../components/CellViews"; import { normalizeCellAlias } from "../../components/CellViews"; import * as Filters from "../../components/Filters/types"; import { allowedFilterOperations } from "../../components/Filters/types/Utility"; import { debounce } from "../../utils/debounce"; import { isBlank, isDefined } from "../../utils/utils"; import { FilterValueRange, FilterValueType, TabFilterType } from "./tab_filter_type"; const operatorNames = Array.from(new Set([].concat(...Object.values(Filters).map((f) => f.map((op) => op.key))))); const Operators = types.enumeration(operatorNames); const getOperatorDefaultValue = (operator) => { if (!operatorNames.includes(operator)) { return null; } return operator === "empty" ? false : null; }; export const TabFilter = types .model("TabFilter", { filter: types.reference(TabFilterType), operator: types.maybeNull(Operators), value: types.maybeNull(FilterValueType), child_filter: types.maybeNull(types.late(() => TabFilter)), }) .views((self) => ({ get field() { return self.filter.field; }, get schema() { return self.filter.schema; }, /** @returns {import("./tab").View} */ get view() { // For child filters, we need to traverse up to find the tab let current = self; let parent = null; try { while (current) { parent = getParent(current); if (parent?.filters && Array.isArray(parent.filters)) { return parent; } current = parent; } } catch { return getParent(getParent(self)); } return null; }, get component() { const operationsList = Filters[self.filter.currentType] ?? Filters.String; return allowedFilterOperations(operationsList, getRoot(self)?.SDK?.type); }, get componentValueType() { return self.component?.find(({ key }) => key === self.operator)?.valueType; }, get target() { return self.filter.field.target; }, get type() { return self.field.currentType; }, get isValidFilter() { const { currentValue: value } = self; if (!isDefined(value) || isBlank(value)) { return false; } if (FilterValueRange.is(value)) { return isDefined(value.min) && isDefined(value.max); } return true; }, get currentValue() { let resultValue; if (self.filter.schema === null) { resultValue = self.value; } else { resultValue = self.value?.value ?? self.value ?? null; } return resultValue; }, get cellView() { const col = self.filter.field; return CellViews[col.type] ?? CellViews[normalizeCellAlias(col.alias)]; }, })) .volatile(() => ({ wasValid: false, saved: false, saving: false, })) .actions((self) => ({ afterAttach() { if (self.value === null) { self.setDefaultValue(); } if (self.operator === null) { self.setOperator(self.component[0].key); } // If this filter's column has child_filter metadata and no child filter exists, create it // This ensures child filters are automatically recreated after navigation if (!self.child_filter && self.filter?.field?.child_filter) { self.view?.applyChildFilter(self); } }, setFilter(value, save = true) { if (!isDefined(value)) return; self.view.clearChildFilter(self); const previousFilterType = self.filter.currentType; const previousFilter = self.filter; self.filter = value; const typeChanged = previousFilterType !== self.filter.currentType; const filterChanged = previousFilter !== self.filter; if (typeChanged || filterChanged) { self.view.applyChildFilter(self); self.markUnsaved(); } if (typeChanged) { self.setDefaultValue(); self.setOperator(self.component[0].key); } if (filterChanged) { self.setValue(null); } if (save) self.saved(); }, setFilterDelayed(value) { self.setFilter(value, false); self.saveDelayed(); }, setOperator(operator) { const previousValueType = self.componentValueType; if (self.operator !== operator) { self.markUnsaved(); self.operator = operator; } if (previousValueType !== self.componentValueType) { self.setDefaultValue(); } self.save(); }, setValue(newValue) { self.value = newValue; }, delete() { self.view.deleteFilter(self); }, save: flow(function* (force = false) { const isValid = self.isValidFilter; if (force !== true) { if (self.saved === true) return; if (isValid === false) return; if (self.wasValid === false && isValid === false) return; } if (self.saving) return; self.saving = true; self.wasValid = isValid; self.markSaved(); getRoot(self)?.unsetSelection(); self.view?.clearSelection(); yield self.view?.save({ interaction: "filter" }); self.saving = false; }), setDefaultValue() { self.setValue(getOperatorDefaultValue(self.operator) ?? self.filter.defaultValue); }, setValueDelayed(value) { self.setValue(value); setTimeout(self.saveDelayed); }, markSaved() { self.saved = true; }, markUnsaved() { self.saved = false; }, saveDelayed: debounce(() => { self.save(); }, 300), })) .preProcessSnapshot((sn) => { if (!sn) return sn; return { ...sn, value: sn.value ?? null }; });