import moment, { type Moment } from 'moment' import { type paramsSerializationOptions } from '../../composables/use-endless-scroll-api' import { isCalendarHeaderInterface, isDefaultSelectHeaderInterface, isEnumSelectHeaderInterface, isMultipleDefaultSelectHeaderInterface, isMultipleEnumSelectHeaderInterface, isMultipleRelationSelectHeaderInterface, isPeriodCalendarHeaderInterface, isRangeNumberHeaderInterface, isRelationSelectHeaderInterface, isTextSearchHeaderInterface, } from '../tables/headers/HeadersTableInterfaces' import { isCheckboxSelectHeaderInterface, isRadioSelectHeaderInterface, isRelationExtendedInterface, isWeekCalendarHeaderInterface, type FilterInterface, } from './FilterInterfaces' import { FilterCalendar, FilterCheckboxSelect, FilterDefaultSelect, FilterDefaultSelectMultiple, FilterEnumSelect, FilterEnumSelectMultiple, FilterPeriodCalendar, FilterRadioSelect, FilterRangeNumber, FilterRelationSelect, FilterRelationSelectMultiple, FilterText, FilterTextSearch, FilterWeekCalendar, } from './filters/index' export interface IFilter { label: string addValue: (value: any) => void removeValue?: (value: any) => void removeValues?: () => void hasValues: () => boolean getValues: () => any[] showValues: () => string getQueryParams: () => { [code: string]: string | number | Array } setParams?: (params: { [code: string]: string | string[] }) => void removeParams?: () => void render: () => JSX.Element } export type FilterValue = string | { id: string, name: string } export class DefaultFilter { label: string constructor({ label }: FilterInterface) { this.label = label } } export class DefaultRelationFilter extends DefaultFilter { searchKey?: string params?: { [code: string]: string | string[] } itemConverter?: (v: any) => { id: string, name: string } paramsSerialization?: paramsSerializationOptions requestPageKey?: string requestPerPageKey?: string responseItemsKey?: string responseTotalKey?: string paginationType?: string additionalItems?: { id: string, name: string }[] constructor(f: FilterInterface) { super(f) if (isRelationExtendedInterface(f.filter)) { if (f.filter.searchKey) this.searchKey = f.filter.searchKey if (f.filter.params) this.params = f.filter.params if (f.filter.itemConverter) this.itemConverter = f.filter.itemConverter if (f.filter.paramsSerialization) this.paramsSerialization = f.filter.paramsSerialization if (f.filter.requestPageKey) this.requestPageKey = f.filter.requestPageKey if (f.filter.requestPerPageKey) this.requestPerPageKey = f.filter.requestPerPageKey if (f.filter.responseItemsKey) this.responseItemsKey = f.filter.responseItemsKey if (f.filter.paginationType) this.paginationType = f.filter.paginationType if (f.filter.additionalItems) this.additionalItems = f.filter.additionalItems } } } export class TextFilter extends DefaultFilter implements IFilter { addValue(value: FilterValue) { if (typeof value !== 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.label = value } // removeValue() {} // // removeValues() {} // eslint-disable-next-line class-methods-use-this hasValues() { return false } getValues() { return [this.label] } showValues() { return String([this.label].length) } // eslint-disable-next-line class-methods-use-this getQueryParams(): { [code: string]: string | string[] } { return {} } render() { return ( ) } } export class TextSearchFilter extends DefaultFilter implements IFilter { value = '' draftValue = '' searchParam!: string constructor(f: FilterInterface) { super(f) if (!isTextSearchHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр TextTableFilter с типом ${f.filter.type}`) } this.searchParam = f.filter.searchParam } addValue(value: FilterValue) { if (typeof value !== 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.draftValue = value } removeValue() { this.draftValue = '' } removeValues() { this.value = '' this.draftValue = '' } hasValues() { return this.value !== '' || this.draftValue !== '' } getValues() { return [this.value] } showValues() { return String([this.value].length) } getQueryParams(): { [code: string]: string | string[] } { const result: { [code: string]: string | string[] } = {} if (this.hasValues()) { result[this.searchParam] = this.value } return result } save() { this.value = this.draftValue } render() { return ( ) } } export class RangeNumberFilter extends DefaultFilter implements IFilter { from: number | undefined = undefined to: number | undefined = undefined fromParam!: string toParam!: string defaultValue?: { from?: number, to?: number } serializationType!: 'default' | 'arkspace' arkspaceSerializationTypeParam?: string constructor(f: FilterInterface) { super(f) if (!isRangeNumberHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр RangeNumberTableFilter с типом ${f.filter.type}`) } this.fromParam = f.filter.fromParam this.toParam = f.filter.toParam this.serializationType = f.filter.serializationType ?? 'default' this.arkspaceSerializationTypeParam = f.filter.arkspaceSerializationTypeParam } addValue(value: { from: number | undefined, to: number | undefined }) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.from = value.from this.to = value.to } removeValue(val: number) { if (this.from === val) { this.from = undefined } else if (this.to === val) { this.to = undefined } } removeValues() { this.from = undefined this.to = undefined } hasValues() { return this.from !== undefined || this.to !== undefined } getValues() { const result: number[] = [] if (this.from !== undefined) { result.push(this.from) } if (this.to !== undefined) { result.push(this.to) } return result } showValues() { let result = '' if (this.from !== undefined) { result += `от ${this.from}` } if (this.to !== undefined) { result += ` до ${this.to}` } return result } getQueryParams() { if (!this.hasValues()) return {} const result: { [code: string]: number | Array } = {} switch (this.serializationType) { case 'arkspace': if (!this.arkspaceSerializationTypeParam) { throw new Error('Параметр arkspaceSerializationTypeParam не задан для NumberRange') } result[this.arkspaceSerializationTypeParam] = [ this.from ? this.from : 'none', this.to ? this.to : 'none', ] break default: if (this.from !== undefined) { result[this.fromParam] = this.from } if (this.to !== undefined) { result[this.toParam] = this.to } } return result } render() { return ( ) } } /** * * Calendars */ export class CalendarFilter extends DefaultFilter implements IFilter { dateParam!: string date: Moment | null = null formatForDisplay = 'DD.MM.YYYY' defaultValue?: Moment saveDefaultFilterValueOnReset?: boolean constructor(f: FilterInterface) { super(f) if (!isCalendarHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр CalendarTableFilter с типом ${f.filter.type}`) } this.dateParam = f.filter.dateParam if (f.filter.defaultValue) { this.defaultValue = f.filter.defaultValue this.date = this.defaultValue } if (f.filter.saveDefaultFilterValueOnReset) { this.saveDefaultFilterValueOnReset = f.filter.saveDefaultFilterValueOnReset } // return this } addValue(value: Moment | null) { if (typeof value !== 'object') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.date = value } removeValue() { this.date = null } removeValues() { this.date = (this.defaultValue && this.saveDefaultFilterValueOnReset) ? this.defaultValue : null } hasValues() { return this.date !== null } getValues() { const result: string[] = [] if (this.date) { result.push(this.date.format(this.formatForDisplay)) } return result } showValues() { let result = '' if (this.date) { result = this.date.format(this.formatForDisplay) } return result } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.date) { result[this.dateParam] = this.date.format('YYYY-MM-DD') } return result } render() { return ( ) } } export class PeriodCalendarFilter extends DefaultFilter implements IFilter { startDateParam!: string endDateParam!: string startDate: Moment | undefined = undefined endDate: Moment | undefined = undefined formatForDisplay = 'DD.MM.YYYY' defaultValue?: { startDate?: Moment, endDate?: Moment } saveDefaultFilterValueOnReset?: boolean constructor(f: FilterInterface) { super(f) if (!isPeriodCalendarHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр PeriodCalendarTableFilter с типом ${f.filter.type}`) } this.startDateParam = f.filter.startDateParam this.endDateParam = f.filter.endDateParam if (f.filter.defaultValue) { this.defaultValue = f.filter.defaultValue } if (this.defaultValue?.startDate) { this.startDate = this.defaultValue.startDate } if (this.defaultValue?.endDate) { this.endDate = this.defaultValue.endDate } if (f.filter.saveDefaultFilterValueOnReset) { this.saveDefaultFilterValueOnReset = f.filter.saveDefaultFilterValueOnReset } // return this } addValue(value: { startDate: Moment | undefined, endDate: Moment | undefined }) { if (typeof value !== 'object') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.startDate = value.startDate this.endDate = value.endDate } removeValue(value: string) { const date: Moment = moment(value, this.formatForDisplay) if (this.startDate?.isSame(date)) { this.startDate = undefined } else if (this.endDate?.isSame(date)) { this.endDate = undefined } } removeValues() { this.startDate = (this.defaultValue?.startDate && this.saveDefaultFilterValueOnReset) ? this.defaultValue.startDate : undefined this.endDate = (this.defaultValue?.endDate && this.saveDefaultFilterValueOnReset) ? this.defaultValue.endDate : undefined } hasValues() { return this.startDate !== undefined || this.endDate !== undefined } getValues() { const result: string[] = [] if (this.startDate) { result.push(this.startDate.format(this.formatForDisplay)) } if (this.endDate) { result.push(this.endDate.format(this.formatForDisplay)) } return result } showValues() { let result = '' if (this.startDate) { result += this.startDate.format(this.formatForDisplay) } if (this.startDate && this.endDate) { result += ' - ' } if (this.endDate) { result += this.endDate.format(this.formatForDisplay) } return result } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.startDate) { result[this.startDateParam] = this.startDate.format('YYYY-MM-DD') } if (this.endDate) { result[this.endDateParam] = this.endDate.format('YYYY-MM-DD') } return result } render() { return ( ) } } export class WeekCalendarFilter extends DefaultFilter implements IFilter { startDateParam!: string endDateParam!: string startDate: Moment | undefined = undefined endDate: Moment | undefined = undefined formatForDisplay = 'DD.MM.YYYY' defaultValue?: { startDate?: Moment, endDate?: Moment } saveDefaultFilterValueOnReset?: boolean constructor(f: FilterInterface) { super(f) if (!isWeekCalendarHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр WeekCalendarTableFilter с типом ${f.filter.type}`) } this.startDateParam = f.filter.startDateParam this.endDateParam = f.filter.endDateParam if (f.filter.defaultValue) { this.defaultValue = f.filter.defaultValue } if (this.defaultValue?.startDate) { this.startDate = this.defaultValue.startDate } if (this.defaultValue?.endDate) { this.endDate = this.defaultValue.endDate } if (f.filter.saveDefaultFilterValueOnReset) { this.saveDefaultFilterValueOnReset = f.filter.saveDefaultFilterValueOnReset } // return this } addValue(value: { startDate: Moment | undefined, endDate: Moment | undefined }) { if (typeof value !== 'object') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.startDate = value.startDate this.endDate = value.endDate } removeValue(value: string) { const date: Moment = moment(value, this.formatForDisplay) if (this.startDate?.isSame(date)) { this.startDate = undefined } else if (this.endDate?.isSame(date)) { this.endDate = undefined } } removeValues() { this.startDate = (this.defaultValue?.startDate && this.saveDefaultFilterValueOnReset) ? this.defaultValue.startDate : undefined this.endDate = (this.defaultValue?.endDate && this.saveDefaultFilterValueOnReset) ? this.defaultValue.endDate : undefined } hasValues() { return this.startDate !== undefined || this.endDate !== undefined } getValues() { const result: string[] = [] if (this.startDate) { result.push(this.startDate.format(this.formatForDisplay)) } if (this.endDate) { result.push(this.endDate.format(this.formatForDisplay)) } return result } showValues() { let result = '' if (this.startDate) { result += this.startDate.format(this.formatForDisplay) } if (this.startDate && this.endDate) { result += ' - ' } if (this.endDate) { result += this.endDate.format(this.formatForDisplay) } return result } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.startDate) { result[this.startDateParam] = this.startDate.format('YYYY-MM-DD') } if (this.endDate) { result[this.endDateParam] = this.endDate.format('YYYY-MM-DD') } return result } render() { return ( ) } } /** * * Relation select */ export class RelationSelectFilter extends DefaultRelationFilter implements IFilter { endpoint!: string selectedParam!: string value: { id: string, name: string } | undefined = undefined defaultParams?: { [code: string]: string | string[] } constructor(f: FilterInterface) { super(f) if (!isRelationSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр RelationSelectTableFilter с типом ${f.filter.type}`) } this.endpoint = f.filter.endpoint || '' this.selectedParam = f.filter.selectedParam this.defaultParams = f.filter.params // return this } addValue(value: FilterValue | undefined) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue() { this.value = undefined } removeValues(): void { this.value = undefined } hasValues() { return this.value !== undefined } getValues() { const result: string[] = [] if (this.hasValues()) { result.push(this.value!.name) } return result } showValues() { const result: string[] = [] if (this.hasValues()) { result.push(this.value!.name) } return String(result.length) } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.hasValues()) { result[this.selectedParam] = this.value!.id } return result } setParams(v: { [code: string]: string | string[] }) { const params = { ...v, ...this.defaultParams } delete params[this.selectedParam] this.params = { ...params } } removeParams() { this.params = {} } render() { return ( ) } } export class MultipleRelationSelectFilter extends DefaultRelationFilter implements IFilter { endpoint!: string selectedParam!: string value: Array<{ id: string, name: string }> = [] defaultParams?: { [code: string]: string | string[] } constructor(f: FilterInterface) { super(f) if (!isMultipleRelationSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр MultipleRelationSelectTableFilter с типом ${f.filter.type}`) } this.endpoint = f.filter.endpoint || '' this.selectedParam = f.filter.selectedParam this.defaultParams = f.filter.params // return this } addValue(value: Array<{ id: string, name: string }>) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue(value: string) { this.value = this.value.filter((item) => item.name !== value) } removeValues(): void { this.value = [] } hasValues() { return Boolean(this.value.length) } getValues() { return this.value.map((item) => item.name) } showValues() { return String(this.value.length) } getQueryParams(): { [code: string]: string[] } { const result: { [code: string]: string[] } = {} if (this.hasValues()) { result[this.selectedParam] = this.value.map((item) => item.id) } return result } setParams(v: { [code: string]: string | string[] }) { const params = { ...v, ...this.defaultParams } delete params[this.selectedParam] this.params = { ...params } } removeParams() { this.params = {} } render() { return ( ) } } /** * * Enum select */ export class EnumSelectFilter extends DefaultRelationFilter implements IFilter { endpoint!: string selectedParam!: string value: { id: string, name: string } | undefined = undefined defaultParams?: { [code: string]: string | string[] } constructor(f: FilterInterface) { super(f) if (!isEnumSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр EnumSelectTableFilter с типом ${f.filter.type}`) } this.endpoint = f.filter.endpoint || '' this.selectedParam = f.filter.selectedParam this.defaultParams = f.filter.params // return this } addValue(value: FilterValue | undefined) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue() { this.value = undefined } removeValues(): void { this.value = undefined } hasValues() { return this.value !== undefined } getValues() { const result: string[] = [] if (this.hasValues()) { result.push(this.value!.name) } return result } showValues() { const result: string[] = [] if (this.hasValues()) { result.push(this.value!.name) } return String(result.length) } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.hasValues()) { result[this.selectedParam] = this.value!.id } return result } setParams(v: { [code: string]: string | string[] }) { const params = { ...v, ...this.defaultParams } delete params[this.selectedParam] this.params = { ...params } } removeParams() { this.params = {} } render() { return ( ) } } export class MultipleEnumSelectFilter extends DefaultRelationFilter implements IFilter { endpoint!: string selectedParam!: string value: Array<{ id: string, name: string }> = [] defaultParams?: { [code: string]: string | string[] } constructor(f: FilterInterface) { super(f) if (!isMultipleEnumSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр MultipleEnumSelectTableFilter с типом ${f.filter.type}`) } this.endpoint = f.filter.endpoint || '' this.selectedParam = f.filter.selectedParam this.defaultParams = f.filter.params // return this } addValue(value: Array<{ id: string, name: string }>) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue(value: string) { this.value = this.value.filter((item) => item.name !== value) } removeValues(): void { this.value = [] } hasValues() { return Boolean(this.value.length) } getValues() { return this.value.map((item) => item.name) } showValues() { return String(this.value.length) } getQueryParams(): { [code: string]: string[] } { const result: { [code: string]: string[] } = {} if (this.hasValues()) { result[this.selectedParam] = this.value.map((item) => item.id) } return result } setParams(v: { [code: string]: string | string[] }) { const params = { ...v, ...this.defaultParams } delete params[this.selectedParam] this.params = { ...params } } removeParams() { this.params = {} } render() { return ( ) } } /** * * Default select */ export class DefaultSelectFilter extends DefaultFilter implements IFilter { selectedParam!: string value: { id: string, name: string } | undefined = undefined options!: { id: string, name: string }[] constructor(f: FilterInterface) { super(f) if (!isDefaultSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр DefaultSelectTableFilter с типом ${f.filter.type}`) } this.selectedParam = f.filter.selectedParam this.options = f.filter.options // return this } addValue(value: FilterValue | undefined) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue() { this.value = undefined } removeValues(): void { this.value = undefined } hasValues() { return this.value !== undefined } getValues() { const result: string[] = [] if (this.value) { result.push(this.value.name) } return result } showValues() { const result: string[] = [] if (this.value) { result.push(this.value.name) } return String(result.length) } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.hasValues()) { result[this.selectedParam] = this.value!.id } return result } render() { return ( ) } } export class MultipleDefaultSelectFilter extends DefaultFilter implements IFilter { selectedParam!: string value: Array<{ id: string, name: string }> = [] options!: { id: string, name: string }[] constructor(f: FilterInterface) { super(f) if (!isMultipleDefaultSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр MultipleDefaultSelectTableFilter с типом ${f.filter.type}`) } this.selectedParam = f.filter.selectedParam this.options = f.filter.options // return this } addValue(value: Array<{ id: string, name: string }>) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue(value: string) { this.value = this.value.filter((item) => item.name !== value) } removeValues(): void { this.value = [] } hasValues() { return Boolean(this.value.length) } getValues() { return this.value.map((item) => item.name) } showValues() { return String(this.value.length) } getQueryParams(): { [code: string]: string[] } { const result: { [code: string]: string[] } = {} if (this.hasValues()) { result[this.selectedParam] = this.value.map((item) => item.id) } return result } render() { return ( ) } } export class RadioSelectFilter extends DefaultFilter implements IFilter { selectedParam!: string value: { id: string, name: string } | undefined = undefined options!: { id: string, name: string }[] constructor(f: FilterInterface) { super(f) if (!isRadioSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр RadioSelectFilter с типом ${f.filter.type}`) } this.selectedParam = f.filter.selectedParam this.options = f.filter.options // return this } addValue(value: FilterValue | undefined) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value = value } removeValue() { this.value = undefined } removeValues(): void { this.value = undefined } hasValues() { return this.value !== undefined } getValues() { const result: string[] = [] if (this.value) { result.push(this.value.name) } return result } showValues() { const result: string[] = [] if (this.value) { result.push(this.value.name) } return String(result.length) } getQueryParams(): { [code: string]: string } { const result: { [code: string]: string } = {} if (this.hasValues()) { result[this.selectedParam] = this.value!.id } return result } render() { return ( ) } } export class CheckboxSelectFilter extends DefaultFilter implements IFilter { selectedParam: string options: Array<{ id: string, name: string }> value: Array<{ id: string, name: string }> = [] constructor(f: FilterInterface) { super(f) if (!isCheckboxSelectHeaderInterface(f.filter)) { throw new Error(`Невозможно собрать фильтр RadioSelectFilter с типом ${f.filter.type}`) } this.options = f.filter.options this.selectedParam = f.filter.selectedParam // return this } addValue(value: FilterValue) { if (typeof value === 'string') { throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`) } this.value.push(value) } removeValue(value: { id: string, name: string }) { this.value = this.value.filter((item) => item.id !== value.id) } removeValues(): void { this.value = [] } hasValue(value: { id: string, name: string }) { return Boolean(this.value.find((item) => item.id === value.id)) } hasValues() { return Boolean(this.value.length) } getValues() { return this.value.map((item) => item.name) } showValues() { return String(this.value.length) } getQueryParams(): { [code: string]: string[] } { const result: { [code: string]: string[] } = {} if (this.hasValues()) { result[this.selectedParam] = this.value.map((item) => item.id) } return result } render() { return ( ) } } export function filterFabric(f: FilterInterface) { const { filter } = f if (isRangeNumberHeaderInterface(filter)) { return new RangeNumberFilter(f) } /** * * Calendars */ if (isCalendarHeaderInterface(filter)) { return new CalendarFilter(f) } if (isPeriodCalendarHeaderInterface(filter)) { return new PeriodCalendarFilter(f) } if (isWeekCalendarHeaderInterface(filter)) { return new WeekCalendarFilter(f) } /** * * Relation select */ if (isRelationSelectHeaderInterface(filter)) { return new RelationSelectFilter(f) } if (isMultipleRelationSelectHeaderInterface(filter)) { return new MultipleRelationSelectFilter(f) } /** * * Enum select */ if (isEnumSelectHeaderInterface(filter)) { return new EnumSelectFilter(f) } if (isMultipleEnumSelectHeaderInterface(filter)) { return new MultipleEnumSelectFilter(f) } /** * * Default select */ if (isDefaultSelectHeaderInterface(filter)) { return new DefaultSelectFilter(f) } if (isMultipleDefaultSelectHeaderInterface(filter)) { return new MultipleDefaultSelectFilter(f) } if (isRadioSelectHeaderInterface(filter)) { return new RadioSelectFilter(f) } if (isCheckboxSelectHeaderInterface(filter)) { return new CheckboxSelectFilter(f) } if (isTextSearchHeaderInterface(filter)) { return new TextSearchFilter(f) } return new TextFilter(f) }