From 7cba5ea8a630025c7f2a64fab139dfde14a61f6f Mon Sep 17 00:00:00 2001 From: Luc Claustres Date: Thu, 6 Jul 2023 18:14:27 +0200 Subject: [PATCH] wip: Upgrade to Quasar 2 / Vue 3 - mixins to xomponents - catalog activity(#393) --- src/composables/composable.alerts.js | 108 +++++++++++++++++++++++++++ src/composables/composable.plan.js | 106 ++++++++++++++++++++++++++ src/composables/index.js | 2 + 3 files changed, 216 insertions(+) create mode 100644 src/composables/composable.alerts.js create mode 100644 src/composables/composable.plan.js create mode 100644 src/composables/index.js diff --git a/src/composables/composable.alerts.js b/src/composables/composable.alerts.js new file mode 100644 index 00000000..ba415c18 --- /dev/null +++ b/src/composables/composable.alerts.js @@ -0,0 +1,108 @@ +import _ from 'lodash' +import moment from 'moment' +import i18next from 'i18next' +import { ref, computed, watch, onBeforeMount, onBeforeUnmount } from 'vue' +import { utils as kCoreUtils } from '@kalisio/kdk/core.client' + +export function useAlerts(options) { + // Functions + function formatAlertDateTime (date) { + return date.toLocaleString(kCoreUtils.getLocale(), + { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) + } + function getAlertDetailsAsHtml (alert) { + const isActive = _.get(alert, 'status.active') + const hasError = _.get(alert, 'status.error') + const checkedAt = new Date(_.get(alert, 'status.checkedAt')) + const triggeredAt = new Date(_.get(alert, 'status.triggeredAt')) + let html = getAlertLocationName(alert) + if (html) html += '
' + _.forOwn(alert.conditions, (value, key) => { + // Get corresponding variable + const variable = _.find(_.get(alert, 'layer.variables'), { name: key }) + const label = i18n.t(variable.label) || variable.label + const unit = variable.units[0] + if (_.has(value, '$gte')) { + html += isActive + ? `${label} ` + i18n.t('CatalogActivity.ALERT_GTE') + ` ${value.$gte} ${unit}
` + : `${label} ` + i18n.t('CatalogActivity.ALERT_LTE') + ` ${value.$gte} ${unit}
` + } + if (_.has(value, '$lte')) { + html += isActive + ? `${label} ` + i18n.t('CatalogActivity.ALERT_LTE') + ` ${value.$lte} ${unit}
` + : `${label} ` + i18n.t('CatalogActivity.ALERT_GTE') + ` ${value.$lte} ${unit}
` + } + }) + html += i18n.t('CatalogActivity.ALERT_CHECKED_AT') + ` ${formatAlertDateTime(checkedAt)}
` + if (isActive) { + // Order triggers by time to get nearest one easily, take care to unify weather/measure triggers + const triggers = _.sortBy(_.get(alert, 'status.triggers', + [trigger => moment.utc(trigger.time || trigger.forecastTime).valueOf()])) + const lastTrigger = _.last(triggers) + const firstTrigger = _.head(triggers) + // Check for forecast (future) or measure (past) + const lastTriggerAt = moment.utc(lastTrigger.time || lastTrigger.forecastTime) + const firstTriggerAt = moment.utc(firstTrigger.time || firstTrigger.forecastTime) + const now = moment.utc() + const nearestTrigger = (Math.abs(now.diff(firstTriggerAt)) < Math.abs(now.diff(lastTriggerAt)) ? firstTrigger : lastTrigger) + const nearestTriggerAt = new Date(nearestTrigger.time || nearestTrigger.forecastTime) + html += (nearestTrigger === lastTrigger ? i18n.t('CatalogActivity.ALERT_LAST') : i18n.t('CatalogActivity.ALERT_FIRST')) + html += i18n.t('CatalogActivity.ALERT_THRESHOLD_AT') + ` ${formatAlertDateTime(nearestTriggerAt)}
` + html += i18n.t('CatalogActivity.ALERT_TRIGGERED_AT') + ` ${formatAlertDateTime(triggeredAt)}
` + } + if (hasError) { + html += '
' + + i18n.t('errors.' + _.get(alert, 'status.error.data.translation.key')) + '
' + } + return html + } + function getAlertStatusAsHtml (alert) { + const isActive = _.get(alert, 'status.active') + const hasError = _.get(alert, 'status.error') + let html = '' + if (isActive) html += '' + i18n.t('CatalogActivity.ALERT_ACTIVE') + '
' + else html += '' + i18n.t('CatalogActivity.ALERT_INACTIVE') + '
' + // Layer name can be a translation key + html += (i18n.t(`${alert.layer.name}`) ? i18n.t(`${alert.layer.name}`) : `${alert.layer.name}`) + if (_.has(alert, 'feature')) { + // Try with default feature labels + let featureLabel = _.get(alert, 'properties.name', _.get(alert, 'properties.NAME')) + // Override if provided by layer + if (_.has(alert, 'layer.featureLabel')) featureLabel = _.get(alert, 'properties.' + _.get(alert, 'layer.featureLabel')) + if (featureLabel) html += ` - ${featureLabel}` + } + if (hasError) { + html += '
' + + i18n.t('errors.' + _.get(alert, 'status.error.data.translation.key')) + '
' + } + return html + } + function getAlertLocationName (alert) { + let name = _.get(alert, 'layer.name', '') + if (_.has(alert, 'feature')) { + // Try with default feature labels + name = _.get(alert, 'properties.name', _.get(alert, 'properties.NAME')) + // Override if provided by layer + if (_.has(alert, 'layer.featureLabel')) name = _.get(alert, 'properties.' + _.get(alert, 'layer.featureLabel')) + } + // Can be a translation key + return (i18n.t(name) ? i18n.t(name) : name) + } + function loadAlertLayer (alert) { + if (!_.has(alert, 'layer._id')) return null + let i18n = _.get(alert, 'layer.i18n') + // Process i18n + if (i18n) { + const locale = kCoreUtils.getAppLocale() + i18n = _.get(i18n, locale) + if (i18n) i18next.addResourceBundle(locale, 'kdk', i18n, true, true) + } + } + + return { + getAlertDetailsAsHtml, + getAlertStatusAsHtml, + loadAlertLayer + } +} + diff --git a/src/composables/composable.plan.js b/src/composables/composable.plan.js new file mode 100644 index 00000000..ad533d25 --- /dev/null +++ b/src/composables/composable.plan.js @@ -0,0 +1,106 @@ +import _ from 'lodash' +import { ref, computed, watch, onBeforeMount, onBeforeUnmount } from 'vue' +import { useRouter, useRoute } from 'vue-router' +import { api } from '@kalisio/kdk/core.client' + +export function usePlan(options) { + + // Data + const route = useRoute() + const planId = ref(null) + const plan = ref(null) + const objectiveFilters = ref([]) + let planService = '' + + // Functions + function hasPlan () { + return planId.value + } + function hasPlanLocation () { + return _.has(plan.value, 'location.latitude') && _.has(plan.value, 'location.longitude') + } + function hasPlanObjectives () { + return (_.get(plan.value, 'objectives', []).length > 0) + } + async function loadPlan (query = {}) { + if (!planId.value) { + plan.value = null + } else { + plan.value = await api.getService(planService, options.contextId).get(planId.value, { query }) + } + } + function onPlanUpdated (updatedPlan) { + if (plan.value && (updatedPlan._id === plan.value._id)) { + plan.value = updatedPlan + } + } + function onPlanRemoved (removedPlan) { + if (plan.value && (removedPlan._id === plan.value._id)) { + plan.value = null + planId.value = null + } + } + function getPlanQuery () { + const query = {} + if (!_.isEmpty(planId.value)) { + Object.assign(query, { + plan: planId.value + }) + } + return query + } + function refreshPlanId () { + const id = _.get(route, 'query.plan', null) + if (planId.value !== id) planId.value = id + } + function getPlanObjectiveQuery () { + if (_.isEmpty(objectiveFilters.value)) return {} + else return { objective: { $in: objectiveFilters.value } } + } + async function countEvents (query = {}) { + const eventsService = api.getService('archived-events', options.contextId) + const response = await eventsService.find({ query: Object.assign(query, getPlanQuery()), $limit: 0 }) + return response.total + } + async function countClosedEvents (query = {}) { + const eventsService = api.getService('archived-events', options.contextId) + const response = await eventsService.find({ query: Object.assign(query, { deletedAt: { $exists: true } }, getPlanQuery()), $limit: 0 }) + return response.total + } + + // Lifecycle hooks + watch(() => route.to, refreshPlanId) + + onBeforeMount(() => { + // Jump to archive whenever required + planService = (_.get(route, 'name').includes('archived') ? 'archived-plans' : 'plans') + refreshPlanId() + const plansService = api.getService(planService, options.contextId) + // Keep track of changes once plan is loaded + plansService.on('patched', onPlanUpdated) + plansService.on('updated', onPlanUpdated) + plansService.on('removed', onPlanRemoved) + }) + + // Cleanup for appendItems + onBeforeUnmount(() => { + const plansService = api.getService(planService, options.contextId) + plansService.off('patched', onPlanUpdated) + plansService.off('updated', onPlanUpdated) + plansService.off('removed', onPlanRemoved) + }) + + return { + plan, + planId, + objectiveFilters, + hasPlan, + hasPlanLocation, + hasPlanObjectives, + loadPlan, + getPlanQuery, + getPlanObjectiveQuery, + countEvents, + countClosedEvents + } +} diff --git a/src/composables/index.js b/src/composables/index.js new file mode 100644 index 00000000..86b0dc92 --- /dev/null +++ b/src/composables/index.js @@ -0,0 +1,2 @@ +export * from './composable.plan.js' +export * from './composable.alerts.js'