import { RulesLogic } from 'json-logic-js' import { AttributeValue, User } from './element.interface' import { FilterData } from './history.interface' import { IListingConfig } from './listing.interface' import { TemplateButton } from './templateButton.interface' // eslint-disable-next-line @typescript-eslint/ban-types export type AttributeKey = string & {} export interface Generic { /** Internal comment */ $comment?: string /** Default attribute value */ // eslint-disable-next-line @typescript-eslint/no-explicit-any default?: any /** Attribute description */ description?: string /** List of attribute examples */ // eslint-disable-next-line @typescript-eslint/no-explicit-any examples?: any[] /** Non-editable schema */ readOnly?: boolean // Attribute user-friendly name title?: string } export type NativeType = | 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' export interface StringProp extends Generic { /** https://json-schema.org/understanding-json-schema/reference/string.html?highlight=string#format */ format: | 'date-time' | 'time' | 'date' | 'email' | 'idn-email' | 'hostname' | 'idn-hostname' | 'ipv4' | 'ipv6' | 'uri' | 'uri-reference' | 'iri' | 'iri-reference' | 'uri-template' | 'json-pointer' | 'relative-json-pointer' | 'regex' /** https://json-schema.org/understanding-json-schema/reference/string.html?highlight=string#length */ maxLength?: number minLength?: number /** https://json-schema.org/understanding-json-schema/reference/string.html?highlight=string#id6 */ pattern?: string type: 'string' } export interface NumericProp extends Generic { /** https://json-schema.org/understanding-json-schema/reference/numeric.html?highlight=number#range */ exclusiveMaximum?: number exclusiveMinimum?: number maximum?: number minimum?: number /** https://json-schema.org/understanding-json-schema/reference/numeric.html?highlight=number#multiples */ multipleOf?: number type: 'integer' | 'number' } export interface BooleanProp extends Generic { type: 'boolean' } export interface ObjectProp extends Generic { /** Enable additional = not-defined properties */ additionalProperties?: boolean /** https://json-schema.org/understanding-json-schema/reference/object.html?highlight=object#dependencies */ dependencies?: Record /** https://json-schema.org/understanding-json-schema/reference/object.html?highlight=object#size */ maxProperties?: number minProperties?: number /** * List of internal properties but defined by regex not specific name * https://json-schema.org/understanding-json-schema/reference/object.html?highlight=object#pattern-properties */ patternProperties?: Record /** List of internal properties */ properties?: Record /** * Regex for custom property names = does not have to be defined, * https://json-schema.org/understanding-json-schema/reference/object.html?highlight=object#property-names */ propertyNames?: { pattern: string } /** Non-editable schema */ readOnly?: boolean /** https://json-schema.org/understanding-json-schema/reference/object.html?highlight=object#property-names */ required?: string[] type: NativeType } /* simpleSchemaId or types - url safe schema id "scope-Car" or "Car" (shared is implied) schemaId - simple schema id (without #) "scope/Car" $id - full schema id (with #) e.g. "#scope/Car" */ export type SharedScope = 'shared' export type Scope = string | SharedScope // eslint-disable-next-line @typescript-eslint/ban-types export type Type = string & {} export type SchemaId = `${Scope}/${Type}` export type $ID = `#${SchemaId}` export type ElementType = `${Scope}-${Type}` | Type export type TemplateId = string export type SchemaIdTemplate = `${SchemaId}/${TemplateId}` export type $SchemaIdTemplate = `#${SchemaIdTemplate}` export type $IDOptionalTemplate = $ID | $SchemaIdTemplate export interface Reference extends Generic { $ref: string } export interface List extends Generic { items: Reference type: 'array' } export interface ElementBuiltIn { $ref: '#Element' projection?: AttributeKey[] size?: 'md' | 'lg' query?: string querySearchAttribute?: AttributeKey allowCreate?: boolean } export interface ButtonBuiltIn { $ref: '#Button' } export interface DropdownButtonBuiltIn { $ref: '#DropdownButton' enum: string[] transitions?: Transition[] } export interface DropdownBuiltIn { $ref: '#Dropdown' enum: string[] dynamicEnum?: string columnView?: boolean enumFilter?: string[] } export interface CheckBoxBuiltIn { $ref: '#CheckBox' enum: string[] } export interface ImgMapBuiltIn { $ref: '#ImgMap' } export interface DateBuiltIn { $ref: '#Date' events?: DateEvent[] } export interface DateTimeBuiltIn { $ref: '#DateTime' events?: DateEvent[] format?: string } export interface GeoBuiltIn { $ref: '#Geo' } export interface TextAreaBuiltIn { $ref: '#TextArea' valueTemplate?: string } export interface TextBuiltIn { $ref: '#Text' valueTemplate?: string listingByValue?: ListingByValueConfig } export interface NumberBuiltIn { $ref: '#Number' isCounting?: boolean countingTemplate?: string listingByValue?: ListingByValueConfig } export interface RelationDataBuiltIn { $ref: '#RelationData' relationAttribute: AttributeKey relationProjection: AttributeKey[] dataAttributes: AttributeKey[] // has to be defined in type schema of `relationAttribute`'s value. showCommentsModal?: boolean cardGridColumns?: number } export interface TaxpayerIdBuiltIn { $ref: '#TaxpayerId' } type ElementHash = string export interface ListingByValueConfig { query: IListingConfig autoRedirectIfSingleResult?: boolean } export interface RelationDataValue { data: Record> } export interface ExternalFileBase { type: string md5: string organizationId: string name: string archived?: boolean relevant?: boolean attribute?: string author: string } export interface SharepointExternalFile extends ExternalFileBase { type: 'sharepoint' documentId: string } export interface SmbExternalFile extends ExternalFileBase { type: 'smb' logicalShareName: string sharePath: string } export interface SharepointExternalFileExtended extends SharepointExternalFile { elementAttribute?: string elementHash?: string elementType?: string projected?: boolean } export type ExternalFile = SharepointExternalFile | SmbExternalFile export interface ExternalFilesValue { data: ExternalFile[] } export interface TableBuiltIn { $ref: '#Table' attributeSource?: AttributeKey columnsSourceType?: AttributeKey // Stringified JSON, parsed and typed in component tableDefinition: string tableType?: 'static' | 'dynamic' } export interface RecurrenceBuiltIn { $ref: '#Recurrence' } type DynamicString = string export interface UserBuiltIn { $ref: '#User' filter?: { securityGroups?: string[] // if set, will filter users only from these security groups users?: DynamicString | string[] // if set, will filter users only from these users } } export interface MarkdownBuiltIn { $ref: '#Markdown' } export interface SecurityGroupBuiltIn { $ref: '#SecurityGroup' } export interface ExternalFileBuiltIn { $ref: '#ExternalFile' dynamicFolderPath?: string } export type BuiltIn = | DropdownBuiltIn | DropdownButtonBuiltIn | UserBuiltIn | ElementBuiltIn | CheckBoxBuiltIn | ImgMapBuiltIn | GeoBuiltIn | TableBuiltIn | TextBuiltIn | TextAreaBuiltIn | NumberBuiltIn | RecurrenceBuiltIn | ExternalFileBuiltIn | MarkdownBuiltIn | SecurityGroupBuiltIn export interface AttributeList extends List { items: T } export type Attribute = | AttributeList | Reference // Could be Built-In | BuiltIn | StringProp | NumericProp | BooleanProp | ObjectProp export interface ElementSchema extends ObjectProp { /** Unique Schema ID */ $id: $ID /** DB Record ID */ id?: string /** Human readable name */ title: string /** Type */ type: NativeType /** Organization scope */ scope: Scope organizationId?: string[] // Used for legacy shared types that belong to organizations (will show up in editors) legacyId?: string // references v1 id parentSchemaId?: string putLegacyOverride?: boolean // if true, will update legacyId even if scope is "shared" } export type AttributeUsed = { key: string mostCommonRef: BuiltIn['$ref'] count: number } export interface GetAttributesResponse { attributes: AttributeUsed[] } // Editors V1 export enum AttributeTypeV1 { TEXT = 'Text', NUMBER = 'Number', TEXTAREA = 'Textarea', DATETIME = 'DateTime', DATE = 'Date', DATERANGE = 'DateRange', FILE = 'File', GEO = 'Geo', DROPDOWN = 'Dropdown', ELEMENT = 'Element', USER = 'User', LISTING = 'Listing', RELATION = 'Relation', DIRECTIVE = 'Directive', } // Editors V2 export enum AttributeType { TEXT = 'Text', MARKDOWN = 'Markdown', ELEMENT = 'Element', BUTTON = 'Button', FILE = 'File', GEO = 'Geo', DROPDOWN = 'Dropdown', DROPDOWN_BUTTON = 'DropdownButton', USER = 'User', TEXTAREA = 'TextArea', NUMBER = 'Number', ARCHIVED = 'Archived', DATETIME = 'DateTime', DATE = 'Date', IFRAME = 'IFrame', TABLE = 'Table', LISTING = 'Listing', IMG_MAP = 'ImgMap', CHECKBOX = 'CheckBox', RELATION_DATA = 'RelationData', RECURRENCE = 'Recurrence', EXTERNAL_FILE = 'ExternalFile', LOM_TABLE = 'LomTable', OMM_SECTIONS = 'OmmSections', TAXPAYER_ID = 'TaxpayerId', SECURITY_GROUP = 'SecurityGroup', LEAD_TIME_TABLE = 'LeadTimeTable', } export interface SchemaEntity { schema: ElementSchema scope: string[] } export interface ScopeResponse { definitions: Record scopeName: string } export interface BaseWidget extends Record { id?: string // For custom use cases renderRule?: 'ALWAYS' | RulesLogic group: 'widget' type: WidgetType } interface BaseLayout { /** Enable Collapsible element = default on/off/login condition */ collapsible?: { defaultOpen: 'OPEN' | 'CLOSED' | RulesLogic } group: 'layout' /** Parent Heading */ heading?: string /** Conditional rendering config */ renderRule?: RulesLogic type: string /** Display as group = background, shadow, border ... */ visualGroup?: boolean id?: string // For custom use cases } /** Vertical layout component */ export interface RowsLayout extends BaseLayout { /** Rows definitions */ rows?: { /** Children definition */ value: Layout | Widget }[] /** id */ type: 'RowsLayout' } export interface ColumnsLayoutItem { /** Children component */ value: Layout | Widget /** COlumn width in % or auto when empty */ width?: { lg?: number; md?: number; xs?: number } } /** Horizontal Group component */ export interface ColumnsLayout extends BaseLayout { /** Columns definitions */ columns?: ColumnsLayoutItem[] /** id */ type: 'ColumnsLayout' } export interface TabItem { /** Tab name - link label / heading */ title: string /** Children definition */ value: Layout | Widget renderRule?: RulesLogic } export interface TabsLayout extends BaseLayout { /** Tabs definitions */ tabs?: TabItem[] type: 'TabsLayout' /** UI variant */ variant: 'vertical' | 'horizontal' | 'accordion' } export interface AttributeItemConfigBase { hideInfo?: boolean hideKey?: boolean key: AttributeKey labelOverride?: string forceRenderMode?: 'read' | 'update' | 'create' renderRule?: 'ALWAYS' | 'EXISTS' | RulesLogic } export interface FileAttributeConfig extends AttributeItemConfigBase { defaultShowArchived?: boolean renderRelevantAsCheckbox?: boolean defaultVariant?: 'list' | 'grid' } export interface ElementAttributeConfig extends AttributeItemConfigBase { projectionResolution?: 'merge' | 'replace' // How to handle projection from AttributeSchema and Template projection?: AttributeKey[] // if resolution is "replace", this take precedent sizeOverride?: 'md' | 'lg' } /** File Listing interfaces */ export type FileListingConfig = { archivePreviousVersion?: boolean // when editing documents, archive the old version projectedAttributes: AttributeKey[] // attributes to show files from (including from listing) mergedProjectedAttributes?: Record // more attributes are merging into other one fileLockAttribute?: AttributeKey // DEPRECATED: if should prevent opening files open by someone else (now using Redis) projectionArchiveAttribute?: AttributeKey // where to store info about archived files from listing projectionRelevantAttribute?: AttributeKey // where to store info about relevant files from listing attributesFromElement?: AttributeKey[] // attributes that inherit values from element sortingAttribute?: string // attribute on element where to store sorted file hashes allowedFileExtensions?: string[] // allowed file extensions to render in component externalFileAttribute?: string // attribute providing source of external files masterProjectionAttributes?: string[] // projected attributes on which files will be forced to uploaded to filenameCustomUpload?: { suffix: string[] prefix: string[] } filenameRegExp?: string[] renderRelevantAsCheckboxAttributes?: string[] fileFilter?: RulesLogic } export interface ImportElementsConfig { initialData: Record< string, Record< string, string | number | string[] | number[] | boolean | null | undefined > > } export interface UnifiedFileData { sourceElementHash: string filename: string timestamp: string } export interface PseudoArchivedFile { archivedBy: User fileHash: string timestamp: string } export interface PseudoRelevantFile { relevantBy: User fileHash: string timestamp: string } // Master tag inverts the upload logic, so that files are uploaded to the master projection instead of the current element export const MASTER_PROJECTION_TAG = 'file_listing_master_projection' // DEPREDATED: use Redis instead, readmore: files.fileLock methods export type LockedFilesValue = { fileHash: string openedBy: User timestamp: string }[] export type ProjectionArchiveValue = PseudoArchivedFile[] /** File Listing interfaces END */ interface ViewModeConfig { view?: AttributeKey[] editable?: AttributeKey[] } type SummaryExpression = { key: AttributeKey expression: string }[] export type InlineElementsConfig = { sourceAttribute?: AttributeKey sourceListing?: IListingConfig sourceToAttribute?: AttributeKey // if parent is used, the children will be created from the parent element data. Otherwise, the children will be created from the source element data childrenMaster?: 'parent' | 'source' // if childrenMaster is parent, these attributes will be copied from source childrenInheritSourceAttributes?: AttributeKey[] // if set, will allow creating new elements of this type allowCreateType?: string // if set, will link the children through this attribute instead of parent parentToAttribute?: AttributeKey hideElementType?: boolean newElementsStartSelected?: boolean displayAttributes?: { create?: ViewModeConfig edit?: ViewModeConfig read?: ViewModeConfig } summaryExpressions?: { create?: SummaryExpression edit?: SummaryExpression } sort?: { [attribute: string]: 1 | -1 } } interface Transition { name: string from: string to: string icon?: string color?: string } export interface RequestStatusFlow { transitions: Transition[] } /* WIDGET CONFIGS */ export type WidgetType = | 'AttributesList' | 'Listing' | 'Checklist' | 'Calendar' | 'AI' | 'Comments' | 'Records' | 'RemoteVideo' | 'RequestStatus' | 'ElementNavigation' | 'FileListingWidget' | 'History' | 'ImageMap' | 'StaticText' | 'SubElements' | 'WorkRecord' | 'InlineElements' | 'GrafanaPanel' | 'Navigation' | 'FeaturesQuery' | 'RelationDataTable' | 'LeadTimeOverview' | 'IotControl' | 'ImportElements' export interface AttributesListWidgetConfig extends BaseWidget { attributes?: AttributeItemConfigBase[] type: 'AttributesList' variant?: 'list' // we can add more variants later } export interface ListingWidgetConfig extends BaseWidget { query?: string // parsed to IListingConfig selectionActions?: TemplateButton[] cardGridColumns?: number type: 'Listing' } export interface CalendarWidgetConfig extends BaseWidget { allowedViewElTypes?: string[] from?: string query: string to?: string type: 'Calendar' } export interface CommentsWidgetConfig extends BaseWidget { sort?: 'asc' | 'desc' type: 'Comments' } export interface RemoteVideoWidgetConfig extends BaseWidget { type: 'RemoteVideo' videoUrl: string checkVideoAvailability?: boolean videoTitle?: string } export interface AIWidgetConfig extends BaseWidget { type: 'AI' agentVendor: 'elevenlabs' agentId: string } export interface ChecklistWidgetConfig extends BaseWidget { type: 'Checklist' } export interface RequestStatusWidgetConfig extends BaseWidget, RequestStatusFlow { type: 'RequestStatus' } export interface ElementNavigationWidgetConfig extends BaseWidget { attributeSource: AttributeKey backgroundSource: AttributeKey type: 'ElementNavigation' } export interface FileListingWidgetConfig extends BaseWidget, FileListingConfig { type: 'FileListingWidget' } export interface ImportElementsWidgetConfig extends BaseWidget, ImportElementsConfig { type: 'ImportElements' } export type GaugeControlConfig = { type: 'gauge' min?: number max?: number step: number unit: string } export type SwitchControlConfig = { type: 'switch' options: { value: string label: string }[] } export type SliderControlConfig = { type: 'slider' min: number max: number step: number unit: string } export type MultiSwitchControlConfig = { type: 'multiSwitch' options: { value: string label: string }[] } export type ControlConfig = | GaugeControlConfig | SwitchControlConfig | SliderControlConfig | MultiSwitchControlConfig export interface IotControlWidgetConfig extends BaseWidget { type: 'IotControl' valueSource: string controlConfig: ControlConfig } export interface HistoryWidgetConfig extends BaseWidget { filterData: FilterData type: 'History' } export interface ImageMapWidgetConfig extends BaseWidget { config: string type: 'ImageMap' } export interface StaticTextWidgetConfig extends BaseWidget { type: 'StaticText' value: string } export interface SubElementsWidgetConfig extends BaseWidget { projection?: AttributeKey[] type: 'SubElements' } export interface FeaturesQueryWidgetConfig extends BaseWidget { query: string // corresponds to FeaturesQuery stringified featureButtons?: TemplateButton[] labelProperty?: string displayMode?: 'list' | 'map' type: 'FeaturesQuery' } export interface WorkRecordsWidgetConfig extends BaseWidget { actions: string[] type: 'WorkRecord' } export interface RecordsWidgetConfig extends BaseWidget { actions: string[] type: 'Records' projection?: AttributeKey[] } export interface InlineElementsWidgetConfig extends BaseWidget, InlineElementsConfig { type: 'InlineElements' } export interface GrafanaPanelWidgetConfig extends BaseWidget { type: 'GrafanaPanel' panelUrl: string injectCss?: string height?: number panelConfigAttribute?: string showDatePickers?: boolean } export type RelationDataTableColumnBase = { width?: number } export type RelationDataTableAttributeColumn = RelationDataTableColumnBase & { source: 'element' | 'relation' attribute: string } export type RelationDataTableCommentColumn = RelationDataTableColumnBase & { source: 'comments' title: string } export type RelationDataTableButtonColumn = RelationDataTableColumnBase & { source: 'button' button: TemplateButton title: string } export type RelationDataTableColumn = | RelationDataTableAttributeColumn | RelationDataTableCommentColumn | RelationDataTableButtonColumn export interface RelationDataTableWidgetConfig extends BaseWidget { type: 'RelationDataTable' relationDataAttribute: string listingConfig: IListingConfig columnDefinitions: RelationDataTableColumn[] showElementComments?: boolean } export interface LeadTimeOverviewWidgetConfig extends BaseWidget { type: 'LeadTimeOverview' supplierAttribute: string leadTimeTableAttribute: string } // Used as attribute value for grafana dashboard panel JSON config export interface GrafanaDashboardPanelConfig { fieldConfig: { defaults: { custom: { thresholdsStyle: { mode: string } } thresholds: { mode: 'absolute' | 'relative' steps: { color: string value: null | number }[] } } } } /** * List of available layouts * keep configuration as simple as possible * type = "xxxWidget" * rest attributes = optional * * TODO: add ui group = enable custom class-name / customize */ export type Layout = RowsLayout | ColumnsLayout | TabsLayout /** * List of available Feature Widgets * keep configuration as simple as possible * type = "xxxLayout" * rest attributes = optional */ export type Widget = BaseWidget /** Layout or Widget assigned to Template as root UI layout config */ export type TemplateLayout = Layout | Widget export type TemplateLayoutType = Layout['type'] | Widget['type'] | string export type AttributeModifier = { id: string condition: RulesLogic effects: { textColor?: string backgroundColor?: string prefix?: string suffix?: string } } /** Element Template config */ export type Template = { _id?: $SchemaIdTemplate // database ID id: $SchemaIdTemplate layout: TemplateLayout attributeModifiers?: Record labels?: { saveLabelTranslationId?: string } preferredElementHashes?: string[] schemaId?: $ID // this is wronly named on backend template?: TemplateId parentTemplateId?: string } export const PALETTES = [ 'danger', 'dark', 'gray', 'info', 'light', 'link', 'primary', 'secondary', 'success', 'warning', ] as const export type PalettesTuple = typeof PALETTES // readonly ['hearts', 'diamonds', 'spades', 'clubs'] export type Palettes = PalettesTuple[number] // "hearts" | "diamonds" | "spades" | "clubs" export type SimplePalette = Partial> export type PaletteValues = { contrastText: string dark: string light: string main: string } export type Palette = Record export interface ThemeBase { bodyBg: string headingColor: string radius: { large: string medium: string normal: string tiny: string } shadow: { button: string /* black with 30% opacity */ input: string inputHover: string nvbar: string /* black with 10% opacity */ thead: string tooltip: string } shape: { borderRadius: string } spacing: { 0: string 1: string 2: string 3: string 4: string 5: string } textColor: string typography: { button: { fontWeight: number lineHeight: number textTransform: string } } } export type Theme = ThemeBase & { palette: Palette } export type SimpleTheme = ThemeBase & { palette: SimplePalette } /** * Summary of a template history entry (without content) */ export interface TemplateHistorySummary { /** MongoDB ObjectId as string */ id: string /** Timestamp when the change was made */ at: Date | string /** User ID who made the change */ author: string } /** * Full template history entry with content */ export interface TemplateHistoryEntry extends TemplateHistorySummary { /** The template data at this point in history */ value: Omit } /** * Summary of a schema history entry (without content) */ export interface SchemaHistorySummary { /** MongoDB ObjectId as string */ id: string /** Timestamp when the change was made */ at: Date | string /** User ID who made the change */ author: string } /** * Full schema history entry with content */ export interface SchemaHistoryEntry extends SchemaHistorySummary { /** The original schema ID this history entry belongs to */ originalId: string /** The schema data at this point in history (MongoDB document format) */ value: { _id: string id: string scope: string schema: ElementSchema legacyId?: string parentSchemaId?: string | null } } interface DateEvent { name: string projectedAttributes: string[] boundary: 'start' | 'end' }