import dayjs from 'dayjs' import { RulesLogic } from 'json-logic-js' import { Element } from '../models/element' import type { ApiResponse } from '../services/api/apisauce' import { Prettify } from '../util/prettify' import { AttributeValue, ElementDataWrapper } from './element.interface' import { TemplateButton } from './templateButton.interface' export type DateFormat = string | dayjs.Dayjs | Date export interface OnElementCriteria { collection: string | string[] type: 'relation' | 'reference' criteria: Prettify } export type ExistsCriterium = { $exists: boolean } export type NotEqualCriterium = { $ne: T } export type NotCriterium = { $not: T } export type Criterium = | AttributeValue | DateQuery | LocationQuery | InQuery | ExistsCriterium | OnElementCriteria | NotEqualCriterium | NotCriterium export type RawCriterium = | DateQuery | LocationQuery | InQueryFormatted | ExistsCriterium | OnElementCriteria | NotEqualCriterium | NotCriterium | string | number export type CriteriumKey = | `${string}.value` | `${string}.file` | 'organization' | 'timestamp' | 'hash' | 'updatedAt' | 'updatedBy' | '$or' // Helper operator shapes export type ComparisonOperators = { $gt?: T $gte?: T $lt?: T $lte?: T } export type DateQuery = ComparisonOperators type NumberComparison = number | ComparisonOperators // Criteria object for keys like `${attribute}.file` export type FileAttributeCriteria = { filename?: string file_size?: NumberComparison hash?: string created_at?: DateQuery relevant?: boolean archived?: boolean } // Map any `${something}.file` key to FileAttributeCriteria export type FileCriteria = { [K in `${string}.file`]?: FileAttributeCriteria } // Map any `${something}.value` key to RawCriterium export type ValueCriteria = { [K in `${string}.value`]?: T } // Meta attributes behave like `.value` without the suffix export type MetaAttributeKey = | 'organization' | 'timestamp' | 'hash' | 'updatedAt' | 'updatedBy' export type MetaCriteria = { [K in MetaAttributeKey]?: K extends 'organization' ? string : T } // Allow using attribute keys without the `.value` suffix; this is the default // Using `string & NonNullable` prevents widening to a generic string index when creating object literals export type AllowedBareKey = Exclude< string & NonNullable, `${string}.file` | `${string}.value` | MetaAttributeKey | '$or' > type RawCriteriaBase = Prettify< Partial< ValueCriteria & FileCriteria & MetaCriteria > > type RawCriteriaStrict = RawCriteriaBase & { $or?: RawCriteria[] } type RawCriteriaLoose = { [K in string & NonNullable]?: unknown } export type RawCriteria = RawCriteriaLoose | RawCriteriaStrict export type BareValueCriteria = Partial< Record > type QueryCriteriaStrict = Prettify< Partial< BareValueCriteria & ValueCriteria & MetaCriteria > & { $or?: QueryCriteria[] } > type QueryCriteriaLoose = { [K in string & NonNullable]?: unknown } export type QueryCriteria = QueryCriteriaLoose | QueryCriteriaStrict export interface ListingQuery { collection: string | string[] | null criteria: QueryCriteria projection: Record element?: Record paginate?: { 'per-page': number page: number } sort: Record atTime?: string preventDefaultArchive?: boolean } export interface RawListingQuery { collection?: string | string[] | null criteria: RawCriteria projection: Record paginate?: IQueryOptions['paginate'] sort?: IQueryOptions['sort'] groups?: boolean atTime?: string preventDefaultArchive?: boolean } export type ExtendedListingValue = { cardCommentsModal?: boolean cardGridColumns?: number query: IListingConfig selectionActions?: TemplateButton[] } export interface ListingRequestData { query: RawListingQuery element: Record } export type SortCriteria = Record export type LocationQuery = { $near: { $geometry: { type: string coordinates: [number, number] } $maxDistance: number } } export type InQuery = { $in: AttributeValue[] } export type InQueryFormatted = { $in: string[] } interface QrLabelsBase { qrSourceAttr: string attrPositions: string[] showAttrNames: boolean includeElementType?: boolean } export interface QRLabels extends QrLabelsBase { includeCurrentElements: QrLabelsBase } // export const perPageArray = [10, 20, 50, 100, 250] // type perPageOptionsTuple = typeof perPageArray // export type perPageOptions = typeof perPageArray export interface IListingConfig { // frontend displayName?: string // Human readable name of listing config name?: string // system name of listing config disabledCriteria?: string[] // which criteria should be disabled for edit staticElementTypes?: boolean // if true, element types cannot be changed cardView?: boolean // show card view by default cardAttributeColumns?: boolean // if undefined, default to true. Respect false to disable displayMode?: 'table' | 'map' | 'calendar' // default display mode sumRow?: boolean // show sum row in listing hideAttributes?: string[] // which attribute columns should be hidden highlightCriteria?: string[] // which criteria should be highlighted, others will be hidden columnWidths?: Record // attribute column widths attributesInEditMode?: string[] // which attributes can be edited qrLabels?: QRLabels tags?: string[] // backend type: string | string[] | null criteria: QueryCriteria projection: string[] sort?: SortCriteria | SortCriteria[] page?: number limit?: number localSort?: (results: Element[]) => Promise localSortByDistance?: boolean localSortByElementDistance?: Element atTime?: string preventDefaultArchive?: boolean } export type ListingHeaders = { 'x-allow-async-listing'?: 'true' | 'false' 'x-sync-children'?: 'true' | 'false' } export type ListingOptions = { allowAsyncListing?: boolean syncChildren?: boolean } export type ListingGet = ( type: string | string[] | null, criteria: RawCriteria, projection: string[], sort?: Record, page?: number, limit?: number, atTime?: string, queryParams?: Record, options?: ListingOptions, preventDefaultArchive?: boolean, ) => Promise> export interface ListingService { get: ListingGet } export type SerializableListingParams = Parameters export type ParsedListingConfig = Omit & { criteria: RawCriteria } & { type: string[] | null parsed: true } export interface ParsedListingParams { params: SerializableListingParams dataHash: string parsedConfig: ParsedListingConfig tags?: string[] } export interface ListingLogicConfig { logic: RulesLogic type: string[] } export interface IListingFilter { key: string type?: string value: | string | number | DateQuery | IDateGteCriterion | IDateLtCriterion | IDateLteCriterion | ILocationCriterion | { $ne: string } | { $near: Record } | IOnElementQuery | null | string[] filterBlock?: number logicOperation?: | 'EQUAL' | 'NOTEQUAL' | 'IN' | 'NOTIN' | '≥' | '>' | '<' | '≤' | 'CONTAINSWORD' } export interface ILocationData { location: { lat: number lng: number } | null radius: number } export interface IOnElementQuery { collection: string type: 'relation' | 'reference' criteria?: { [key: string]: IListingFilter['value'] } & { $or?: IOnElementQuery['criteria'][] } } // IListingConfig on frontend export interface IListingPreset { type: string | string[] | null showInGraph?: string[] sortBy?: string sortReverse?: 1 | -1 filter?: (IListingFilter | IListingFilter[])[] relationFilters?: CombinedCriteria['onElementCriteria'] recordFilters?: IListingRecordFilter[] pagination?: { page: number limit: number } locationData?: ILocationData showGraph?: boolean showTable?: boolean filterCollapsed?: boolean showArchived?: boolean countNumberAttributes?: boolean } interface IListingRecordFilter { due_date: string actions: string name: string author: string } export interface CombinedCriteria { basicCriteria: IListingFilter[] onElementCriteria?: { collection: string attribute: string criteria?: IListingFilter[] type?: 'relation' | 'reference' } } interface ILocationCriterion { $near: { $geometry: { type: 'Point'; coordinates: [number, number] } $maxDistance: number } } export interface IDateGteCriterion { $gte: string | Date } export interface IDateLtCriterion { $lt: string | Date } export interface IDateLteCriterion { $lte: string | Date } export interface IListingElementsResponse { elements: T[] count: number asyncListingInitiated?: boolean } export interface IQueryOptions { paginate: { page: number 'per-page': number } showArchived?: boolean sort: { [key: string]: 1 | -1 } atTime?: string }