Support to dynamic dashboard (#219)
* First test for dynamic load * add support to list dashboard items * change some dashboards names Co-authored-by: Yamel Senih <ysenih@erpya.com>pull/3759/head
							parent
							
								
									d622eec48b
								
							
						
					
					
						commit
						f0630d79d6
					
				| 
						 | 
				
			
			@ -347,6 +347,10 @@ export function requestPrintFormats({ tableName, reportViewUuid, processUuid })
 | 
			
		|||
  return Instance.call(this).requestPrintFormats({ tableName: tableName, reportViewUuid: reportViewUuid, processUuid: processUuid })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function requestLisDashboards(roleUuid) {
 | 
			
		||||
  return Instance.call(this).requestDashboards(roleUuid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function requestLanguages() {
 | 
			
		||||
  return Instance.call(this).requestLanguages()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <component
 | 
			
		||||
    :is="renderDashboard"
 | 
			
		||||
    :ref="dashboard.dashboardName"
 | 
			
		||||
    :metadata="dashboard"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'Dashboard',
 | 
			
		||||
  props: {
 | 
			
		||||
    metadata: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      dashboard: this.metadata
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // load the component that is indicated in the attributes of received property
 | 
			
		||||
    renderDashboard() {
 | 
			
		||||
      if (this.metadata.fileName === 'userfavorites') {
 | 
			
		||||
        return () => import('@/components/ADempiere/Dashboard/favourites')
 | 
			
		||||
      }
 | 
			
		||||
      return () => import(`@/components/ADempiere/Dashboard/${this.metadata.fileName}`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.dashboard-editor-container {
 | 
			
		||||
  padding: 32px;
 | 
			
		||||
  background-color: rgb(240, 242, 245);
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  .github-corner {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chart-wrapper {
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    padding: 16px 16px 0;
 | 
			
		||||
    margin-bottom: 32px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-collapse v-model="activeRecentItems" accordion>
 | 
			
		||||
    <el-collapse-item name="recentItems">
 | 
			
		||||
      <template slot="title">
 | 
			
		||||
        <i class="el-icon-time" style="margin-right: 4px;margin-left: 10px;" /> {{ $t('profile.recentItems') }}
 | 
			
		||||
      </template>
 | 
			
		||||
      <el-card class="box-card" :body-style="{ padding: '0px' }" shadow="never">
 | 
			
		||||
        <div class="recent-items">
 | 
			
		||||
          <el-table :data="search.length ? filterResult(search) : recentItems" max-height="455" @row-click="handleClick">
 | 
			
		||||
            <el-table-column width="40">
 | 
			
		||||
              <template slot-scope="{row}">
 | 
			
		||||
                <svg-icon :icon-class="row.icon" class="icon-window" />
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column>
 | 
			
		||||
              <template slot="header" slot-scope="scope" class="clearfix">
 | 
			
		||||
                <el-input
 | 
			
		||||
                  v-model="search"
 | 
			
		||||
                  size="mini"
 | 
			
		||||
                  :metadata="scope"
 | 
			
		||||
                  :placeholder="$t('table.dataTable.search')"
 | 
			
		||||
                />
 | 
			
		||||
              </template>
 | 
			
		||||
              <template slot-scope="{row}">
 | 
			
		||||
                <span>{{ row.displayName }}</span>
 | 
			
		||||
                <el-tag class="action-tag">{{ $t(`views.${row.action}`) }}</el-tag>
 | 
			
		||||
                <br>
 | 
			
		||||
                <span class="time">{{ translateDate(row.updated) }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
          </el-table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-card>
 | 
			
		||||
    </el-collapse-item>
 | 
			
		||||
  </el-collapse>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { getRecentItems as getRecentItemsFromServer } from '@/api/ADempiere'
 | 
			
		||||
import { convertAction } from '@/utils/ADempiere/dictionaryUtils'
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'RecentItems',
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      activeRecentItems: 'recentItems',
 | 
			
		||||
      recentItems: [],
 | 
			
		||||
      isLoaded: true,
 | 
			
		||||
      search: '',
 | 
			
		||||
      accentRegexp: /[\u0300-\u036f]/g
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    getterRecentItems() {
 | 
			
		||||
      return this.$store.getters.getRecentItems
 | 
			
		||||
    },
 | 
			
		||||
    cachedViews() {
 | 
			
		||||
      return this.$store.getters.cachedViews
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.getRecentItems()
 | 
			
		||||
    this.subscribeChanges()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    checkOpened(uuid) {
 | 
			
		||||
      return this.cachedViews.includes(uuid)
 | 
			
		||||
    },
 | 
			
		||||
    getRecentItems() {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        getRecentItemsFromServer()
 | 
			
		||||
          .then(response => {
 | 
			
		||||
            const recentItems = response.getRecentitemsList().map(item => {
 | 
			
		||||
              const actionConverted = convertAction(item.getAction())
 | 
			
		||||
              return {
 | 
			
		||||
                action: actionConverted.name,
 | 
			
		||||
                icon: actionConverted.icon,
 | 
			
		||||
                displayName: item.getDisplayname(),
 | 
			
		||||
                menuUuid: item.getMenuuuid(),
 | 
			
		||||
                menuName: item.getMenuname(),
 | 
			
		||||
                windowUuid: item.getWindowuuid(),
 | 
			
		||||
                tableId: item.getTableid(),
 | 
			
		||||
                recordId: item.getRecordid(),
 | 
			
		||||
                uuidRecord: item.getRecorduuid(),
 | 
			
		||||
                tabUuid: item.getTabuuid(),
 | 
			
		||||
                updated: new Date(item.getUpdated()),
 | 
			
		||||
                description: item.getMenudescription()
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            this.recentItems = recentItems
 | 
			
		||||
            this.isLoaded = false
 | 
			
		||||
            resolve(recentItems)
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => {
 | 
			
		||||
            reject(error)
 | 
			
		||||
          })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    handleClick(row) {
 | 
			
		||||
      if (!this.isEmptyValue(row.uuidRecord)) {
 | 
			
		||||
        this.$router.push({ name: row.menuUuid, query: { action: row.uuidRecord, tabParent: 0 }})
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$router.push({ name: row.menuUuid })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    subscribeChanges() {
 | 
			
		||||
      this.$store.subscribe((mutation, state) => {
 | 
			
		||||
        if (mutation.type === 'notifyDashboardRefresh') {
 | 
			
		||||
          this.getRecentItems()
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    filterResult(search) {
 | 
			
		||||
      return this.recentItems.filter(item => this.ignoreAccent(item.displayName).toLowerCase().includes(this.ignoreAccent(search.toLowerCase())))
 | 
			
		||||
    },
 | 
			
		||||
    ignoreAccent(s) {
 | 
			
		||||
      if (!s) { return '' }
 | 
			
		||||
      return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
 | 
			
		||||
    },
 | 
			
		||||
    translateDate(value) {
 | 
			
		||||
      return this.$d(new Date(value), 'long', this.language)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
  .search_recent {
 | 
			
		||||
    width: 50%!important;
 | 
			
		||||
    float: right;
 | 
			
		||||
  }
 | 
			
		||||
	.header {
 | 
			
		||||
		padding-bottom: 10px;
 | 
			
		||||
	}
 | 
			
		||||
	.recent-items {
 | 
			
		||||
		height: 455px;
 | 
			
		||||
		overflow: auto;
 | 
			
		||||
	}
 | 
			
		||||
  .time {
 | 
			
		||||
    float: left;
 | 
			
		||||
    font-size: 11px;
 | 
			
		||||
    color: #999;
 | 
			
		||||
  }
 | 
			
		||||
  .card-box {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
  .card-content {
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
  }
 | 
			
		||||
  .icon-window {
 | 
			
		||||
    font-size: x-large;
 | 
			
		||||
    color: #36a3f7;
 | 
			
		||||
  }
 | 
			
		||||
  .action-tag {
 | 
			
		||||
    float: right;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
// Default store for handle dashboard refresh and other functionalities
 | 
			
		||||
import { requestLisDashboards } from '@/api/ADempiere/data'
 | 
			
		||||
const dashboard = {
 | 
			
		||||
  state: {
 | 
			
		||||
    dashboard: []
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +15,39 @@ const dashboard = {
 | 
			
		|||
  actions: {
 | 
			
		||||
    refreshDashboard({ commit, getters }, parameters) {
 | 
			
		||||
      commit('notifyDashboardRefresh', parameters)
 | 
			
		||||
    },
 | 
			
		||||
    listDashboard({ commit }, roleUuid) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        requestLisDashboards(roleUuid)
 | 
			
		||||
          .then(response => {
 | 
			
		||||
            const dashboards = response.getDashboardsList().map(item => {
 | 
			
		||||
              return {
 | 
			
		||||
                windowUuid: item.getWindowuuid(),
 | 
			
		||||
                browserUuid: item.getBrowseruuid(),
 | 
			
		||||
                dashboardName: item.getDashboardname(),
 | 
			
		||||
                dashboardDescription: item.getDashboarddescription(),
 | 
			
		||||
                dashboardHtml: item.getDashboardhtml(),
 | 
			
		||||
                columnNo: item.getColumnno(),
 | 
			
		||||
                lineNo: item.getLineno(),
 | 
			
		||||
                isCollapsible: item.getIscollapsible(),
 | 
			
		||||
                isOpenByDefault: item.getIsopenbydefault(),
 | 
			
		||||
                isEventRequired: item.getIseventrequired(),
 | 
			
		||||
                fileName: item.getFilename()
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            const roleDashboards = {
 | 
			
		||||
              roleUuid: roleUuid,
 | 
			
		||||
              recordCount: response.getRecordcount(),
 | 
			
		||||
              dashboardList: dashboards,
 | 
			
		||||
              nextPageToken: response.getNextPageToken()
 | 
			
		||||
            }
 | 
			
		||||
            commit('addDashboard', roleDashboards)
 | 
			
		||||
            resolve(roleDashboards)
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => {
 | 
			
		||||
            reject(error)
 | 
			
		||||
          })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  getters: {
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +55,11 @@ const dashboard = {
 | 
			
		|||
      return state.dashboard.find(
 | 
			
		||||
        item => item.uuid === dashboardUuid
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    getDashboardByRole: (state) => (roleUuid) => {
 | 
			
		||||
      return state.dashboard.find(
 | 
			
		||||
        item => item.roleUuid === roleUuid
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,108 +1,48 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="dashboard-editor-container">
 | 
			
		||||
    <github-corner class="github-corner" />
 | 
			
		||||
 | 
			
		||||
    <panel-group @handleSetLineChartData="handleSetLineChartData" />
 | 
			
		||||
 | 
			
		||||
    <el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
 | 
			
		||||
      <line-chart :chart-data="lineChartData" />
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row :gutter="32">
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <raddar-chart />
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <pie-chart />
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <bar-chart />
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row :gutter="8">
 | 
			
		||||
      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
 | 
			
		||||
        <recent-items />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
 | 
			
		||||
        <favorites />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
 | 
			
		||||
        <todo-list />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
 | 
			
		||||
        <box-card />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
 | 
			
		||||
        <pending-documents />
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <template v-for="(dashboard, index) in dashboardList">
 | 
			
		||||
        <el-col :key="index" :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
 | 
			
		||||
          <dashboard :metadata="dashboard" />
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import GithubCorner from '@/components/GithubCorner'
 | 
			
		||||
import PanelGroup from './components/PanelGroup'
 | 
			
		||||
import LineChart from './components/LineChart'
 | 
			
		||||
import RaddarChart from './components/RaddarChart'
 | 
			
		||||
import PieChart from './components/PieChart'
 | 
			
		||||
import BarChart from './components/BarChart'
 | 
			
		||||
// import TransactionTable from './components/TransactionTable'
 | 
			
		||||
import TodoList from './components/TodoList'
 | 
			
		||||
import BoxCard from './components/BoxCard'
 | 
			
		||||
import RecentItems from '@/components/ADempiere/RecentItems'
 | 
			
		||||
import Favorites from '@/components/ADempiere/Favorites'
 | 
			
		||||
import PendingDocuments from '@/components/ADempiere/PendingDocuments'
 | 
			
		||||
 | 
			
		||||
const lineChartData = {
 | 
			
		||||
  newVisitis: {
 | 
			
		||||
    expectedData: [100, 120, 161, 134, 105, 160, 165],
 | 
			
		||||
    actualData: [120, 82, 91, 154, 162, 140, 145]
 | 
			
		||||
  },
 | 
			
		||||
  messages: {
 | 
			
		||||
    expectedData: [200, 192, 120, 144, 160, 130, 140],
 | 
			
		||||
    actualData: [180, 160, 151, 106, 145, 150, 130]
 | 
			
		||||
  },
 | 
			
		||||
  purchases: {
 | 
			
		||||
    expectedData: [80, 100, 121, 104, 105, 90, 100],
 | 
			
		||||
    actualData: [120, 90, 100, 138, 142, 130, 130]
 | 
			
		||||
  },
 | 
			
		||||
  shoppings: {
 | 
			
		||||
    expectedData: [130, 140, 141, 142, 145, 150, 160],
 | 
			
		||||
    actualData: [120, 82, 91, 154, 162, 140, 130]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import Dashboard from '@/components/ADempiere/Dashboard'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'DashboardAdmin',
 | 
			
		||||
  components: {
 | 
			
		||||
    GithubCorner,
 | 
			
		||||
    PanelGroup,
 | 
			
		||||
    LineChart,
 | 
			
		||||
    RaddarChart,
 | 
			
		||||
    PieChart,
 | 
			
		||||
    BarChart,
 | 
			
		||||
    // TransactionTable,
 | 
			
		||||
    TodoList,
 | 
			
		||||
    BoxCard,
 | 
			
		||||
    RecentItems,
 | 
			
		||||
    Favorites,
 | 
			
		||||
    PendingDocuments
 | 
			
		||||
    Dashboard
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      lineChartData: lineChartData.newVisitis
 | 
			
		||||
      roleUuid: this.$store.getters.getRoleUuid,
 | 
			
		||||
      dashboardList: []
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    getterDashboard() {
 | 
			
		||||
      return this.$store.getters.getDashboardByRole(this.roleUuid)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.getDashboardListFromServer()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    handleSetLineChartData(type) {
 | 
			
		||||
      this.lineChartData = lineChartData[type]
 | 
			
		||||
    getDashboardListFromServer() {
 | 
			
		||||
      if (this.getterDashboard) {
 | 
			
		||||
        this.dashboardList = this.getterDashboard
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$store.dispatch('listDashboard', this.roleUuid)
 | 
			
		||||
          .then(response => {
 | 
			
		||||
            this.dashboardList = response.dashboardList
 | 
			
		||||
          })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue