vbea пре 2 година
родитељ
комит
bd85c0535d

+ 137 - 0
Strides-Admin/src/views/charging/LoadBalance.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      
+    </div>
+    <el-table
+      :data="table.data"
+      class="no-border"
+      v-loading="loading">
+      <el-table-column
+        label="Site Name"
+        prop="sitePk"
+        align="center"
+        min-width="150">
+        <template slot-scope="{row}">
+          <span class="link-type" @click="onEditClick(row)">{{ row.siteName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="Address"
+        prop="address"
+        align="center"
+        min-width="180"/>
+      <el-table-column
+        label="Service Provider"
+        align="center"
+        min-width="150">
+        <template slot-scope="{row}">
+          <div
+            v-for="(item, index) in row.serviceProviders"
+            :key="index">
+            {{item.providerName}}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="Load Balancing Type"
+        prop="loadBalancingType"
+        align="center"
+        min-width="150"/>
+      <el-table-column
+        v-if="!$route.meta.onlyView"
+        label="Action"
+        align="center"
+        min-width="70">
+        <template slot-scope="{row}">
+          <el-dropdown
+            class="action-dropdown"
+            @command="(v) => handleCommand(v, row)">
+            <i class="el-icon-more icon-action"></i>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item
+                command="onEditClick">
+                Edit Info
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="right">
+      <pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="filter.pageNo"
+        :limit.sync="filter.pageSize"
+        @pagination="getTableData" />
+    </div>
+  </div>
+</template>
+
+<script>
+import api from '@/http/api/apiBalancing.js';
+import Pagination from '@/components/Pagination'
+import TableAction from '@/components/TableAction.vue'
+export default {
+  data() {
+    return {
+      loading: false,
+      filter: {
+        pageNo: 1,
+        pageSize: 10,
+        pageVo: {
+          criteria: ""
+        }
+      },
+      table: {
+        data: [],
+        total: 0
+      },
+    };
+  },
+  components: {Pagination, TableAction},
+  created() {
+    this.onSearch();
+  },
+  methods: {
+    onSearch() {
+      this.filter.pageNo = 1;
+      this.getTableData();
+    },
+    getTableData() {
+      this.loading = true;
+      api.getBalancingPages(this.filter).then(res => {
+        if (res.data && res.total) {
+          this.table.data = res.data
+          this.table.total = res.total
+        } else {
+          this.table.data = []
+          this.table.total = 0
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.table.data = []
+        this.table.total = 0
+      }).finally(() => {
+        this.loading = false;
+      })
+    },
+    handleCommand(cb, item) {
+      this[cb](item)
+    },
+    onEditClick(row) {
+      this.$router.push({
+        path: '/smart-energy-management/site-load-balancing/' + row.sitePk
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 258 - 0
Strides-Admin/src/views/charging/LoadBalanceView.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="view-container" v-loading="loading">
+    <div class="flexr">
+      <div class="view-content card">
+        <div class="section-title">Connector Utilization Rate Today</div>
+        <CircleChart
+          v-if="detailData.connectorUtilization"
+          :chartData="detailData.connectorUtilization"/>
+      </div>
+      <div class="view-content form">
+        <div class="section-title">Hourly Utilization(kWH)</div>
+        <LineChart :chartData="detailData.hourlyUsage"/>
+      </div>
+    </div>
+    <div class="flexr">
+      <div class="view-content card">
+        <div class="section-title">Power Delivery Summary</div>
+        <div class="flexcw">
+          <div class="summary-text">Highest Delivered (KWH)</div>
+          <div class="summary-text">{{detailData.highestDelivered}}</div>
+        </div>
+        <div class="flexcw">
+          <div class="summary-text">Lowest Delivered (KWH)</div>
+          <div class="summary-text">{{detailData.lowestDelivered}}</div>
+        </div>
+        <div class="flexcw">
+          <div class="summary-text">Total Connectors</div>
+          <div class="summary-text">{{detailData.connectorUtilization.allConnectorCount || "0"}}</div>
+        </div>
+        <div class="flexcw">
+          <div class="summary-text">Max Connectors Used</div>
+          <div class="summary-text">{{detailData.staticMaxAmpere || "0"}}</div>
+        </div>
+      </div>
+      <el-form class="view-content form">
+        <div class="flexcr">
+          <div class="section-title">LOAD BALANCING Settings</div>
+          <div class="section-sub-title" v-if="detailData.siteName">(<b>Site Name:</b> {{detailData.siteName}})</div>
+        </div>
+        <Balancing v-model="balancingForm" :isEdit="true"/>
+      </el-form>
+    </div>
+    <div class="view-content flexcr">
+      <div class="buttons">
+        <el-button
+          class="cancel-button"
+          type="primary"
+          @click="onBack">
+          Cancel
+        </el-button>
+        <el-button
+          type="primary"
+          @click="onClickSave">
+          Save
+        </el-button>
+      </div>
+      <div class="update-by">
+        <span
+          class="add-text"
+          :title='"CREATED BY " + detailData.createdBy + " ON " + detailData.createdOn'>
+          LAST UPDATED BY {{detailData.updatedBy}} TIMESTAMP: {{detailData.updatedOn}}
+        </span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import api from '@/http/api/apiBalancing.js';
+import Balancing from '../site/components/Balancing';
+import LineChart from '../dashboard/chart/LineChart.vue';
+import CircleChart from '../dashboard/chart/CircleChart.vue';
+export default {
+  data() {
+    return {
+      loading: false,
+      detailData: {
+        sitePk: "",
+        siteName: "",
+        hourlyUsage: {
+          xdata: [],
+          ydata: []
+        },
+        highestDelivered: "",
+        lowestDelivered: "",
+        connectorUtilization: {}
+      },
+      balancingForm: {
+        sitePk: "",
+        loadBalancing: 'dynamic',
+        staticMaxAmpere: 0,
+        siteChargingProfiles: [{
+          boxInUse: '',
+          chargingProfilePk: ''
+        }]
+      },
+    };
+  },
+  components: {Balancing,CircleChart,LineChart},
+  created() {
+    if (this.$route.params.id) {
+      this.getBalanceDetail();
+    }
+  },
+  methods: {
+    onBack() {
+      this.$router.push({
+        path: "/smart-energy-management/site-load-balancing"
+      });
+    },
+    getBalanceDetail() {
+      this.loading = true;
+      api.viewBalancing({
+        sitePk: this.$route.params.id,
+      }).then(res => {
+        if (res.data) {
+          this.detailData = res.data;
+          this.balancingForm = {
+            sitePk: this.detailData.sitePk,
+            loadBalancing: this.detailData.loadBalancing || 'dynamic',
+            staticMaxAmpere: this.detailData.staticMaxAmpere || 0,
+            siteChargingProfiles: this.detailData.siteChargingProfiles || [{
+              boxInUse: '',
+              chargingProfilePk: ''
+            }]
+          }
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.loading = false;
+      })
+    },
+    onClickSave() {
+      if (this.balancingForm.loadBalancing == 'dynamic') {
+        this.balancingForm.staticMaxAmpere = ""
+      }
+      const chargeProfiles = []
+      this.balancingForm.siteChargingProfiles.forEach(item => {
+        if (item.boxInUse && item.chargingProfilePk)
+          chargeProfiles.push(item)
+      })
+      if (chargeProfiles.length > 0) {
+        this.balancingForm.siteChargingProfiles = chargeProfiles;
+      } else {
+        this.$message({
+          message: "Please add at least one charging profile",
+          type: 'error'
+        })
+        return;
+      }
+      this.loading = true;
+      api.updateBalancing(this.balancingForm).then(res => {
+        this.$message({
+          message: "Saved successfully",
+          type: 'success',
+          duration: 3000
+        })
+        this.onBack();
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.loading = false;
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang='scss'>
+  @import '../../styles/variables.scss';
+  
+  .view-container {
+    width: 100%;
+    padding: 20px 40px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+  }
+  
+  .view-content {
+    margin: 0 8px 16px;
+    padding: 15px 40px;
+    border-radius: 6px;
+    background-color: white;
+    &.card {
+      flex: 1;
+      min-width: 200px;
+      max-width: 320px;
+      padding-bottom: 30px;
+    }
+    &.form {
+      flex: 1;
+      min-width: 300px;
+    }
+  }
+  
+  .section-title {
+    color: #333333;
+    margin-top: 20px;
+    margin-bottom: 20px;
+    font-size: 15px;
+    line-height: 24px;
+    font-weight: 700;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  
+  .section-sub-title {
+    color: #333333;
+    font-size: 15px;
+    line-height: 24px;
+    padding-left: 10px;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  
+  .summary-text {
+    color: #333;
+    padding: 5px 0;
+    font-size: 14px;
+    + .summary-text {
+      white-space: nowrap;
+      padding-left: 10px;
+    }
+  }
+  
+  @media screen and (max-width: 800px) {
+    .view-container {
+      padding: 10px 20px;
+    }
+    .view-content {
+      padding: 15px 20px;
+    }
+  }
+  
+  @media screen and (max-width: 620px) {
+    .view-content {
+      max-width: unset !important;
+      margin: 0 8px 10px;
+    }
+  }
+  
+  @media screen and (max-width: 500px) {
+    .view-container {
+      padding: 0px;
+    }
+    .view-content {
+      padding: 15px 20px;
+    }
+  }
+</style>

+ 459 - 0
Strides-Admin/src/views/report/ReportV2.vue

@@ -0,0 +1,459 @@
+<template>
+  <div class="app-container">
+    <div
+      class="filter-container"
+      v-loading="loading.filter">
+      <div class="filter-row">
+        <label class="el-form-item__label">Report Type:</label>
+        <el-select
+          v-model="filter.pageVo.reportType"
+          @change="changeReportType"
+          class="filter-input"
+          placeholder="Filter Report Type"
+          clearable>
+          <el-option
+            v-for="reportType in options.reportType"
+            :label="reportType.name"
+            :value="reportType.value"
+            :key="reportType.value"/>
+        </el-select>
+      </div>
+      <div class="filter-row">
+        <label class="el-form-item__label">Filters:</label>
+        <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          start-placeholder="Start Date"
+          end-placeholder="End Date"
+          :picker-options="pickerOptions"
+          clearable
+          @change="changeDateRange"
+          class="filter-input"
+          v-if="filter.pageVo.reportType == 'APENDIXF'"/>
+        <template v-else>
+          <el-date-picker
+            v-model="filter.pageVo.year"
+            type="year"
+            format="yyyy"
+            value-format="yyyy"
+            :clearable="false"
+            class="filter-input half"
+            placeholder="Filter Year"/>
+          <el-select
+            v-model="filter.pageVo.month"
+            class="filter-input half"
+            placeholder="Filter Month"
+            clearable>
+            <el-option
+              v-for="month in options.monthOptions"
+              :label="month.name"
+              :value="month.value"
+              :key="month.value"/>
+          </el-select>
+        </template>
+        <el-select
+          v-if="filter.pageVo.reportType == 'MNTHTRAN' || filter.pageVo.reportType == 'MNTHSEPR'"
+          v-model="filter.pageVo.providerPks"
+          class="filter-input"
+          placeholder="Service Provider"
+          clearable
+          multiple>
+          <el-option
+            v-for="(item,index) in options.serviceProvider"
+            :label="item.providerName"
+            :value="item.providerPk"
+            :key="index"/>
+        </el-select>
+        <!--:remote-method="(s) => getSiteOptions(s)"-->
+        <el-select
+          clearable
+          filterable multiple
+          v-if="filter.pageVo.reportType == 'MNTHSITE' || filter.pageVo.reportType == 'APENDIXF'"
+          v-model="filter.pageVo.sitePks"
+          class="filter-input"
+          placeholder="Sites">
+          <el-option
+            v-for="(item, index) in options.siteOptions"
+            :key="index"
+            :label="item.siteName"
+            :value="item.sitePk"/>
+        </el-select>
+        <el-select
+          clearable
+          multiple
+          v-if="filter.pageVo.reportType == 'MNTHFLET'"
+          v-model="filter.pageVo.groupPks"
+          class="filter-input"
+          placeholder="Fleet">
+          <el-option
+            v-for="(item, index) in options.groupOptions"
+            :key="index"
+            :label="item.name"
+            :value="item.value"/>
+        </el-select>
+        <el-select
+          clearable
+          multiple
+          v-if="filter.pageVo.reportType == 'MNTHMEMB'"
+          v-model="filter.pageVo.groupPks"
+          class="filter-input"
+          placeholder="Member">
+          <el-option
+            v-for="(item, index) in options.groupOptions"
+            :key="index"
+            :label="item.name"
+            :value="item.value"/>
+        </el-select>
+        <el-select
+          clearable
+          multiple
+          v-if="filter.pageVo.reportType == 'MNTHPART'"
+          v-model="filter.pageVo.groupPks"
+          class="filter-input"
+          placeholder="Partner">
+          <el-option
+            v-for="(item, index) in options.groupOptions"
+            :key="index"
+            :label="item.name"
+            :value="item.value"/>
+        </el-select>
+        <el-button
+          class="generate-button"
+          type="primary"
+          @click="onSearch"
+          :disabled="loading.table">
+          Search
+        </el-button>
+        <el-button
+          class="generate-button"
+          v-waves
+          type="primary"
+          :loading="loading.generate"
+          @click="reGenerateReport">
+          Generate
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      :data="table.data"
+      class="no-border"
+      v-loading="loading.table">
+      <el-table-column
+        label="Report Type"
+        prop="reportType"
+        align="center"
+        min-width="180"/>
+      <el-table-column
+        label="Year"
+        prop="year"
+        align="center"
+        min-width="80"/>
+      <el-table-column
+        label="Month"
+        prop="month"
+        align="center"
+        min-width="80"/>
+      <el-table-column
+        label="Creation Date/Time"
+        prop="createTime"
+        align="center"
+        min-width="150"/>
+      <el-table-column
+        label="Created By"
+        prop="createBy"
+        align="center"
+        min-width="150"/>
+      <el-table-column
+        label="Role"
+        prop="role"
+        align="center"
+        min-width="100"/>
+      <el-table-column
+        label="Name"
+        prop="reportName"
+        align="center"
+        min-width="220"/>
+      <el-table-column
+        v-if="!$route.meta.onlyView"
+        label="Action"
+        align="center"
+        width="130">
+        <template slot-scope="{row}">
+          <el-button
+            :loading="row.loading"
+            icon="el-icon-download"
+            class="export-button"
+            @click="handleExportExcel(row)">
+            Export
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="right">
+      <pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="filter.pageNo"
+        :limit.sync="filter.pageSize"
+        @pagination="getReportsPages" />
+    </div>
+  </div>
+</template>
+
+<script>
+import site from '@/http/api/site'
+import api from '@/http/api/apiReport'
+import provider from '@/http/api/provider'
+import Pagination from '@/components/Pagination'
+import TableAction from '@/components/TableAction.vue'
+
+export default {
+  components: { Pagination, TableAction },
+  data() {
+    return {
+      loading: {
+        filter: false,
+        table: false,
+        generate: false
+      },
+      dateRange: [],
+      filter: {
+        pageNo: 1,
+        pageSize: 10,
+        pageVo: {
+          year: ''+new Date().getFullYear(),
+          month: '',
+          startDate: "",
+          endDate: "",
+          reportType: '',
+          sitePks: [],
+          groupPks: [],
+          providerPks: []
+        }
+      },
+      options: {
+        reportType: [],
+        siteOptions: [],
+        monthOptions: [],
+        groupOptions: [],
+        serviceProvider: []
+      },
+      table: {
+        data: [],
+        total: 0
+      },
+      pickerOptions: {
+        shortcuts: [{
+          text: 'Nearest Week',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+      hasGrouptype: ["MNTHFLET", "MNTHMEMB", "MNTHPART"]
+    }
+  },
+  created() {
+    this.getFilterOptions();
+  },
+  methods: {
+    getFilterOptions() {
+      this.loading.filter = true
+      Promise.all([
+        this.getMonthOptions(),
+        this.getReportTypeOptions(),
+        this.getSiteOptions(),
+        this.getServiceProviderList()
+      ]).then(() => {
+        this.loading.filter = false;
+        this.getReportsPages();
+      }).catch(err => {
+        this.loading.filter = false;
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      })
+    },
+    changeReportType() {
+      if (this.hasGrouptype.indexOf(this.filter.pageVo.reportType) >= 0) {
+        this.getGroupOptions();
+      }
+      this.filter.pageVo = {
+        ...this.filter.pageVo,
+        sitePks: [],
+        groupPks: [],
+        providerPks: []
+      }
+    },
+    changeDateRange(range) {
+      if (this.dateRange.length == 2) {
+        this.filter.pageVo.startDate = this.dateRange[0]
+        this.filter.pageVo.endDate = this.dateRange[1]
+      }
+    },
+    onSearch() {
+      this.filter.pageNo = 1;
+      this.loading.table = true;
+      setTimeout(() => {
+        this.getReportsPages()
+      }, 3000);
+    },
+    reGenerateReport() {
+      this.loading.generate = true;
+      api.generateReport(this.filter.pageVo).then(res => {
+        this.loading.generate = false;
+        this.$message({
+          type: res.success?'success':'error',
+          message: res.msg
+        })
+      }).catch(err => {
+        this.loading.generate = false;
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      })
+    },
+    getServiceProviderList() {
+      return provider.getAllServiceProvider().then(res => {
+        if (res.data) {
+          this.options.serviceProvider = res.data;
+        }
+      })
+    },
+    getMonthOptions() {
+      return api.getMonthList().then(res => {
+        if (res.data) {
+          this.options.monthOptions = res.data
+          this.filter.pageVo.month = res.data[0].value
+        }
+      })
+    },
+    getReportTypeOptions() {
+      return api.getReportTypeList().then(res => {
+        if (res.data) {
+          this.options.reportType = res.data
+          this.filter.pageVo.reportType = res.data[0].value
+        }
+      })
+    },
+    getSiteOptions(search) {
+      site.getAllSiteList({siteName: search ?? ""}).then(res => {
+        if (res.data && res.data.length > 0) {
+          this.options.siteOptions = res.data
+        } else {
+          this.options.siteOptions = []
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.options.siteOptions = []
+      });
+    },
+    getGroupOptions() {
+      api.getGroupList({
+        reportType: this.filter.pageVo.reportType
+      }).then(res => {
+        if (res.data) {
+          this.options.groupOptions = res.data
+        }
+      })
+    },
+    getReportsPages() {
+      this.loading.table = true;
+      api.getReportsPages(this.filter).then(res => {
+        if (res.data) {
+          this.table.data = res.data.map((report) => {
+            report.loading = false
+            return report
+          })
+          this.table.total = res.total
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      }).finally(() => {
+        this.loading.table = false
+      })
+    },
+    handleExportExcel(row) {
+      row.loading = true
+      api.exportReports({ filePk: row.filePk }).then((res) => {
+        this.downloadExcel(res, row.reportName)
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      }).finally(() => {
+        row.loading = false
+      })
+    },
+    downloadExcel(res, fileName) {
+      const blob = new Blob([res], {
+        type: 'application/vnd.ms-excel;charset=utf-8'
+      })
+      if ('download' in document.createElement('a')) {
+        // 非IE下载
+        const elink = document.createElement('a')
+        elink.download = fileName
+        elink.style.display = 'none'
+        elink.href = URL.createObjectURL(blob)
+        document.body.appendChild(elink)
+        elink.click()
+        URL.revokeObjectURL(elink.href) // 释放URL 对象
+        document.body.removeChild(elink)
+      } else {
+        // IE10+下载
+        navigator.msSaveBlob(blob, fileName)
+      }
+      this.excelLoad = false
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+  .filter-row {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    margin-bottom: 15px;
+    .el-form-item__label {
+      color: #333;
+      width: 100px;
+      text-align: right;
+    }
+    .filter-input {
+      flex: 1;
+      min-width: 180px;
+      max-width: 260px;
+      &.half {
+        min-width: 85px;
+        max-width: 125px;
+      }
+      & + .filter-input {
+        margin-left: 10px;
+      }
+    }
+  }
+
+  .generate-button {
+    width: 100px;
+    height: 40px;
+    margin-left: 10px;
+  }
+
+  .export-button {
+    background-color: #fff;
+  }
+</style>

+ 475 - 0
Strides-Admin/src/views/report/Reports.vue

@@ -0,0 +1,475 @@
+<template>
+  <div class="app-container">
+    <div
+      class="filter-container"
+      v-loading="loading">
+      <el-form
+        label-width="130px"
+        :model="reportFilter">
+        <div class="row">
+          <el-form-item
+            label="Report Type:"
+            class="row-item">
+            <el-select
+              v-model="reportFilter.reportType"
+              @change="changeReportType"
+              clearable>
+              <el-option
+                v-for="reportType in reportTypeOptions"
+                :label="reportType.name"
+                :value="reportType.value"
+                :key="reportType.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Service Provider:"
+            v-if="reportFilter.reportType !== 'MTU' && reportFilter.reportType !== 'MAF'"
+            class="row-item">
+            <el-select
+              v-model="reportFilter.providerPk"
+              clearable>
+              <el-option
+                v-for="serviceProvider in serviceProviderOptions"
+                :label="serviceProvider.name"
+                :value="serviceProvider.value"
+                :key="serviceProvider.value"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Site:"
+            class="row-item"
+            v-if="reportFilter.reportType == 'MAF' || reportFilter.reportType == 'MSS'">
+            <el-select
+              clearable
+              filterable
+              remote
+              v-model="reportFilter.sitePk"
+              :remote-method="(s) => getSiteOptions(s)"
+              placeholder="Select with search">
+              <el-option
+                v-for="(item, index) in siteOptions"
+                :key="index"
+                :label="item.siteName"
+                :value="item.sitePk"/>
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <div class="row">
+          <el-form-item
+            label="Year:"
+            class="row-item"
+            v-if="reportFilter.reportType != 'MAF'">
+            <el-date-picker
+              v-model="reportFilter.year"
+              format="yyyy"
+              value-format="yyyy"
+              type="year"
+              :clearable="false"/>
+          </el-form-item>
+          <el-form-item
+            label="Month:"
+            class="row-item"
+            v-if="reportFilter.reportType != 'MAF'">
+            <el-select
+              v-model="reportFilter.month"
+              clearable>
+              <el-option
+                v-for="month in monthOptions"
+                :label="month.name"
+                :value="month.value"
+                :key="month.value"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Date Range:"
+            class="row-item"
+            v-if="reportFilter.reportType == 'MAF'">
+            <el-date-picker
+              v-model="dateRange"
+              type="daterange"
+              value-format="yyyy-MM-dd"
+              start-placeholder="Start Date"
+              end-placeholder="End Date"
+              :picker-options="pickerOptions"
+              clearable/>
+          </el-form-item>
+          <el-button
+            class="filter-item generate-button"
+            v-waves
+            type="primary"
+            @click="handleGenerateReport">
+            Search
+          </el-button>
+          <el-button
+            class="filter-item generate-button"
+            v-waves
+            type="primary"
+            :disabled="regenbtn"
+            @click="reGenerateReport">
+            Generate
+          </el-button>
+        </div>
+      </el-form>
+    </div>
+    <el-table
+      :data="reports"
+      fit
+      style="width: 100%;">
+      <el-table-column
+        label="File Name"
+        prop="fileName"
+        align="center"
+        min-width="400"
+        >
+        <template slot-scope="{row}">
+          <span>{{ row.fileName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="Month"
+        prop="monthStr"
+        align="center"
+        min-width="90"
+      >
+          <template slot-scope="{row}">
+            <span>{{ row.monthStr }}</span>
+          </template>
+      </el-table-column>
+      <el-table-column
+        label="Service Provider"
+        prop="providerName"
+        align="center"
+        min-width="160">
+          <template slot-scope="{row}">
+            <span>{{ row.providerName }}</span>
+          </template>
+      </el-table-column>
+      <el-table-column
+        label="Creation Date/Time"
+        prop="createDateTime"
+        align="center"
+        min-width="180"
+      >
+          <template slot-scope="{row}">
+            <span>{{ row.createDateTime }}</span>
+          </template>
+      </el-table-column>
+      <el-table-column
+        v-if="!$route.meta.onlyView"
+        label="Action"
+        align="center"
+        min-width="160"
+      >
+          <template slot-scope="{row}">
+            <el-button
+              :loading="row.loading"
+              icon="el-icon-download"
+              class="export-button"
+              @click="handleExportExcel(row)">
+              Export
+            </el-button>
+          </template>
+      </el-table-column>
+    </el-table>
+    <div class="right">
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        :page.sync="pagination.page"
+        :limit.sync="pagination.limit"
+        @pagination="handlePageChange" />
+    </div>
+  </div>
+</template>
+
+<script>
+import api from '@/http/api/dashboard'
+import site from '@/http/api/site'
+import reports from '@/http/api/reports'
+
+// waves directive
+import waves from '@/directive/waves'
+// secondary package based on el-pagination
+import Pagination from '@/components/Pagination'
+import TableAction from '@/components/TableAction.vue'
+
+const { getProviderList } = api
+const {
+  getReportTypeList,
+  getMonthList,
+  getReportsPages,
+  downloadFile,
+  generateMonthlyExcel
+} = reports
+
+export default {
+  name: 'Reports',
+  components: { Pagination, TableAction },
+  directives: { waves },
+  setup() {
+    
+  },
+  created() {
+    this.getData()
+    this.getReportsPages()
+  },
+  data() {
+    return {
+      loading: false,
+      regenbtn: false,
+      dateRange: [],
+      siteOptions: [],
+      reportTypeOptions: [],
+      serviceProviderOptions: [],
+      monthOptions: [],
+      yearOptions: [],
+      reportFilter: {
+        sitePk: "",
+        reportType: '',
+        providerPk: '',
+        year: ''+new Date().getFullYear(),
+        month: '',
+      },
+      reports: [],
+      total: 0,
+      pagination: {
+        limit: 10,
+        page: 1,
+      },
+      pickerOptions: {
+        shortcuts: [{
+          text: 'Nearest Week',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+    }
+  },
+  methods: {
+    getData() {
+      this.loading = true
+      Promise.all([
+        this.getServiceProviderTypeOptions(),
+        this.getMonthOptions(),
+        this.getReportTypeOptions(),
+        this.getSiteOptions()
+      ]).then(() => {
+        this.loading = false
+      })
+    },
+    changeReportType() {
+      this.getSiteOptions("")
+    },
+    validate(isGen) {
+      var valid = true;
+      if (this.reportFilter.reportType == 'MAF') {
+        if (this.dateRange.length == 2) {
+          this.reportFilter.dateStart = this.dateRange[0]
+          this.reportFilter.dateEnd = this.dateRange[1]
+        } else if (isGen) {
+          this.$message({
+            type: 'error',
+            message: "Please select date range"
+          });
+          valid = false;
+        }
+      }
+      if (/*this.reportFilter.reportType == 'MSUR' || */this.reportFilter.reportType == 'MAF') {
+        if (!this.reportFilter.sitePk && isGen) {
+          this.$message({
+            type: 'error',
+            message: "Please select a site"
+          });
+          valid = false;
+        }
+      }
+      return valid;
+    },
+    handleGenerateReport() {
+      if (!this.validate()) {
+        return;
+      }
+      this.loading = true
+      this.getReportsPages()
+    },
+    async reGenerateReport() {
+      if (!this.validate(true)) {
+        return;
+      }
+      this.regenbtn = true;
+      this.loading = true;
+      generateMonthlyExcel(this.reportFilter).then(res => {
+        this.loading = false;
+        this.$message({
+          type: res.success?'success':'error',
+          message: res.msg
+        })
+      }).catch(err => {
+        this.loading = false;
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      })
+      
+      setTimeout(() => {
+        this.regenbtn = false;
+      }, 5000);
+    },
+    getServiceProviderTypeOptions() {
+      return getProviderList().then(({ data }) => {
+        this.serviceProviderOptions = data
+        this.reportFilter.providerPk = data[0].value
+      })
+    },
+    getMonthOptions() {
+      return getMonthList().then(({ success, data }) => {
+        if (success) {
+          this.monthOptions = data
+          this.reportFilter.month = data[0].value
+        }
+      })
+    },
+    getReportTypeOptions() {
+      return getReportTypeList()
+        .then(({ success, data }) => {
+          if (success) {
+            this.reportTypeOptions = data
+            this.reportFilter.reportType = data[0].value
+          }
+        })
+    },
+    getSiteOptions(search) {
+      site.getAllSiteList({siteName: search ?? ""}).then(res => {
+        if (res.data && res.data.length > 0) {
+          this.siteOptions = res.data
+        }
+      });
+    },
+    handlePageChange(value) {
+      this.loading = true
+      const { limit, page } = value
+      this.pagination.limit = limit
+      this.pagination.page = page
+      this.getReportsPages()
+    },
+    getReportsPages() {
+      const {
+        limit,
+        page,
+      } = this.pagination
+      const params = {
+        pageSize: limit,
+        pageNo: page,
+        pageVo: this.reportFilter,
+      }
+      return getReportsPages(params)
+        .then(({ success, data, total }) => {
+          if (success) {
+            this.reports = data.map((report) => {
+              report.loading = false
+              return report
+            })
+            this.total = total
+          }
+        }).finally(() => {
+          this.loading = false
+        })
+    },
+    handleExportExcel(row) {
+      row.loading = true
+      downloadFile({ filePk: row.filePk }).then((res) => {
+        this.downloadExcel(res, row.fileName)
+      }).finally(() => {
+        row.loading = false
+      })
+    },
+    downloadExcel(res, fileName) {
+      const blob = new Blob([res], {
+        type: 'application/vnd.ms-excel;charset=utf-8'
+      })
+      // let href = window.URL.createObjectURL(blob)
+      if ('download' in document.createElement('a')) {
+        // 非IE下载
+        const elink = document.createElement('a')
+        elink.download = fileName
+        elink.style.display = 'none'
+        elink.href = URL.createObjectURL(blob)
+        document.body.appendChild(elink)
+        elink.click()
+        URL.revokeObjectURL(elink.href) // 释放URL 对象
+        document.body.removeChild(elink)
+      } else {
+        // IE10+下载
+        navigator.msSaveBlob(blob, fileName)
+      }
+      this.excelLoad = false
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+  @import '../../styles/variables.scss';
+
+  .view-container {
+    width: 100%;
+    padding: 20px 60px;
+    background-color: #F0F5FC;
+  }
+
+  .view-content {
+    padding: 15px 45px;
+    border-radius: 6px;
+    min-height: calc(#{$mainAppMinHeight} - (2 * 20px));
+    background-color: white;
+  }
+
+  .section-title {
+    color: #333;
+    padding-left: 30px;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 15px;
+    user-select: none;
+    line-height: 24px;
+    font-weight: 700;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+
+  .row {
+    display: flex;
+    flex-wrap: wrap;
+  }
+
+  .row-item {
+    position: relative;
+    margin-right: 20px;
+  }
+  
+  .row-item > * {
+    width: 100%;
+  }
+
+  .generate-button {
+    width: 107.9px;
+    height: 40px;
+  }
+
+  .export-button {
+    background-color: #fff;
+  }
+  @media screen and (max-width: 500px) {
+    .view-content {
+      padding: 15px 30px;
+    }
+  }
+</style>

+ 127 - 0
Strides-Admin/src/views/site/historyRate.vue

@@ -0,0 +1,127 @@
+<template>
+  <el-dialog
+    title="VIEW RATE"
+    :visible="visible"
+    :before-close="onHide"
+    class="flexl flexcc"
+    custom-class="history-rate-dialog">
+    <div class="table-view" v-loading="loading">
+      <el-table
+        :data="tableData"
+        class="no-border"
+        height="100%">
+        <el-table-column
+          align="center"
+          label="Rate Name"
+          prop="rateName"
+          min-width="140"/>
+        <el-table-column
+          align="center"
+          label="Repeat"
+          prop="repeat"
+          min-width="120"/>
+        <el-table-column
+          align="center"
+          label="Start Time"
+          prop="startTime"
+          min-width="100"/>
+        <el-table-column
+          align="center"
+          label="End Time"
+          prop="endTime"
+          min-width="100"/>
+        <el-table-column
+          align="center"
+          label="Rate Configured"
+          prop="siteName"
+          min-width="180">
+          <template slot-scope="{row}">
+            <div v-for="item in row.rateConfigs" :key="item">{{item}}</div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import site from '../../http/api/site'
+export default {
+  name: "HistoryRate",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    id: String|Number
+  },
+  data() {
+    return {
+      loading: false,
+      tableData: []
+    };
+  },
+  watch: {
+    visible: {
+      handler(n, o) {
+        if (n && this.id) {
+          this.tableData = []
+          this.getHistoryRate();
+        }
+      }
+    }
+  },
+  mounted() {
+    
+  },
+  methods: {
+    onHide() {
+      this.$emit("hide")
+    },
+    getHistoryRate() {
+      this.loading = true;
+      site.getHistoryRates({
+        sitePk: this.id
+      }).then(res => {
+        if (res.data) {
+          this.tableData = res.data;
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      }).finally(() => {
+        this.loading = false
+      })
+    }
+  }
+}
+</script>
+
+<style>
+.history-rate-dialog {
+  width: 100%;
+  display: flex;
+  max-width: 800px;
+  margin-top: 0 !important;
+  flex-direction: column;
+}
+.history-rate-dialog .el-dialog__body {
+  flex: 1;
+  padding: 20px;
+  display: flex;
+  overflow: hidden;
+  flex-direction: column;
+}
+.history-rate-dialog .table-view {
+  height: 60vh;
+  margin-bottom: 20px;
+}
+@media screen and (max-width: 600px) {
+  .history-rate-dialog .table-view {
+    height: 80vh;
+    margin-bottom: 10px;
+  }
+}
+</style>