All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ducks.regions.sagas.ts Maven / Gradle / Ivy

The newest version!
import { put, takeEvery, cancel, select, call, delay } from 'redux-saga/effects'
import values from 'lodash/values'
import each from 'lodash/each'
import find from 'lodash/find'
import isEmpty from 'lodash/isEmpty'
import get from 'lodash/get'

import { rootPageSelector } from '../global/selectors'
import { makePageRoutesByIdSelector } from '../pages/selectors'
import { mapQueryToUrl } from '../pages/sagas/restoreFilters'
import { modelsSelector } from '../models/selectors'
import {
    makeDatasourceIdSelector,
    makeWidgetFetchOnInit,
    makeWidgetFetchOnVisibility,
    makeWidgetIsInitSelector,
} from '../widgets/selectors'
import { dataRequest, failValidate, resetValidation } from '../datasource/store'
import { dataSourcesSelector } from '../datasource/selectors'
import { State as DataSources } from '../datasource/DataSource'
import { formsSelector } from '../form/selectors'
import { FormsState as Forms } from '../form/types'
import { INVALID_TAB_REDUX_KEY } from '../../components/regions/Tabs/constants'
import { State as ModelsState } from '../models/Models'
import { checkTabAvailability } from '../../components/regions/helpers'
import { State } from '../State'

import { SetTabInvalid, RegisterRegion, ServiceInfo } from './Actions'
import {
    regionsSelector, registerRegion, MAP_URL,
    setRegionServiceInfo, setTabInvalid, setActiveRegion,
} from './store'
import { State as Regions } from './Regions'

const DEFAULT_INFO_META = {
    datasources: [],
    widgets: [],
}

function* createServiceInfo(action: RegisterRegion) {
    const { payload } = action
    const { regionState } = payload
    const { tabs } = regionState

    if (!tabs) {
        yield cancel()
    }

    const { regionId } = regionState

    const serviceInfo: ServiceInfo = {}

    for (const tab of tabs) {
        const { id: tabId, content = [] } = tab

        serviceInfo[tabId] = { ...DEFAULT_INFO_META }

        for (const meta of content) {
            const { id: widgetId, datasource } = meta
            const { widgets, datasources } = serviceInfo[tabId]

            /* collects widgets ids */
            serviceInfo[tabId].widgets = [...widgets, widgetId]

            if (datasource) {
                /* collects ds ids */
                serviceInfo[tabId].datasources = [...datasources, datasource]
            }
        }
    }

    yield put(setRegionServiceInfo(regionId, serviceInfo))
}

function* lazyFetch(id: string) {
    const regions: Regions = yield select(regionsSelector)
    const models: ModelsState = yield select(modelsSelector)
    const regionCollection = values(regions)
    const idsToFetch: string[] = []

    if (!isEmpty(regionCollection)) {
        each(regionCollection, (region) => {
            const { activeEntity, alwaysRefresh } = region

            const targetTab = { ...find(region.tabs, tab => tab.id === activeEntity) }

            if (!isEmpty(targetTab.content)) {
                each(targetTab.content, (item) => {
                    if (
                        (alwaysRefresh && targetTab.id === id) ||
                        !Object.keys(models.datasource).includes(item.datasource || item.id)
                    ) {
                        idsToFetch.push(item.id)
                    }
                })
            }
        })
        for (const widgetId of idsToFetch) {
            const datasource: string = yield select(makeDatasourceIdSelector(widgetId))

            const isInit: boolean = yield select(makeWidgetIsInitSelector(widgetId))
            const fetchOnInit: boolean = yield select(makeWidgetFetchOnInit(widgetId))
            const fetchOnVisibility: boolean = yield select(makeWidgetFetchOnVisibility(widgetId))

            const needToFetch = isInit && (fetchOnInit || fetchOnVisibility)

            if (needToFetch) {
                yield put(dataRequest(datasource))
            }
        }

        idsToFetch.length = 0
    }
}

function* autoSwitch({ payload }: SetTabInvalid) {
    const { invalid } = payload

    if (!invalid) {
        yield cancel()
    }

    yield delay(75)

    const regions: Regions = yield select(regionsSelector)
    const tabsRegions = Object.fromEntries(Object.entries(regions).filter(([, meta]) => meta.tabs))
    const regionsIds = Object.keys(tabsRegions)

    for (const regionId of regionsIds) {
        const region = regions[regionId]
        const { tabs } = region

        const { activeEntity } = region
        const activeTab = tabs.find(({ id }) => id === activeEntity)
        const activeTabIsInvalid = get(activeTab, INVALID_TAB_REDUX_KEY, false)

        /** Если активный tab валидный то переключаем на первый невалидный tab **/
        if (!activeTabIsInvalid) {
            const firstInvalidTab = tabs.find(({ invalid = false }) => invalid)
            const tabId = get(firstInvalidTab, 'id', '')

            if (tabId) {
                yield put(setActiveRegion(regionId, tabId))
                yield mapUrl(tabId)

                yield cancel()
            }
        }
    }

    yield cancel()
}

function* validateTabs() {
    const regions: Regions = yield select(regionsSelector)

    if (isEmpty(regions)) {
        yield cancel()
    }

    const dataSources: DataSources = yield select(dataSourcesSelector)
    const forms: Forms = yield select(formsSelector)
    const tabsRegions = Object.fromEntries(Object.entries(regions).filter(([, meta]) => meta.tabs))

    if (isEmpty(tabsRegions)) {
        yield cancel()
    }

    const state: State = yield select()
    const regionsIds = Object.keys(tabsRegions)

    for (const regionId of regionsIds) {
        const region = regions[regionId]

        const { serviceInfo } = region

        for (const [tabId, service] of Object.entries(serviceInfo)) {
            const { widgets } = service

            const { tabs } = region

            const tab = tabs.find(({ id }) => id === tabId)

            const widgetsState = get(state, 'widgets', {})
            const regionsState = get(state, 'regions', {})

            const available = tab
                ? checkTabAvailability({ serviceInfo, widgetsState, regionsState }, tab, state)
                : false

            /** Проверка tab на содержание невалидных полей **/
            const invalid = available
                ? widgets.some((widget) => {
                    const form = forms[widget]

                    if (!form) {
                        return false
                    }

                    const { datasource = null } = form

                    if (!datasource) {
                        return false
                    }

                    const { fields } = forms[widget]
                    const { errors } = dataSources[datasource]

                    const { resolve, edit } = errors
                    const fieldKeys = Object.keys(fields)
                    const availableFields = fieldKeys.filter(key => fields[key].visible &&
                        !fields[key].disabled_field &&
                        !fields[key].disabled_set)

                    return availableFields.some(key => resolve[key] || edit[key])
                })
                : false

            const currentInvalid = get(tab, INVALID_TAB_REDUX_KEY, false)

            if ((invalid !== currentInvalid)) {
                const { parent } = tabsRegions[regionId]

                /** Вложенные tabs в tabs, установка invalid родительскому tab **/
                if (parent) {
                    const { regionId, tabId } = parent

                    yield put(setTabInvalid(regionId, tabId, invalid))
                }

                yield put(setTabInvalid(regionId, tabId, invalid))
            }
        }
    }
}

function* mapUrl(value: string) {
    const rootPageId: string = yield select(rootPageSelector)
    const routes: Record = yield select(makePageRoutesByIdSelector(rootPageId))

    if (routes) {
        yield call(mapQueryToUrl, rootPageId, null)
        yield call(lazyFetch, value)
    }
}

export const sagas = [
    takeEvery(registerRegion, createServiceInfo),
    takeEvery([failValidate, resetValidation], validateTabs),
    takeEvery(setTabInvalid, autoSwitch),
    // @ts-ignore FIXME переделать на rtk экшен
    takeEvery(MAP_URL, mapUrl),
]




© 2015 - 2024 Weber Informatics LLC | Privacy Policy