vbea 3 سال پیش
والد
کامیت
5e830124b4
23فایلهای تغییر یافته به همراه2485 افزوده شده و 176 حذف شده
  1. 3 0
      Strides-Admin/src/http/api/charge.js
  2. 36 0
      Strides-Admin/src/router/SiteRouter.js
  3. 2 2
      Strides-Admin/src/router/index.js
  4. 6 0
      Strides-Admin/src/styles/index.scss
  5. 4 4
      Strides-Admin/src/views/Administrator.vue
  6. 6 7
      Strides-Admin/src/views/charge/AddStation.vue
  7. 72 41
      Strides-Admin/src/views/charge/Connectors.vue
  8. 129 45
      Strides-Admin/src/views/charge/RegisteredChargeStations.vue
  9. 1 3
      Strides-Admin/src/views/charging/EditProfile.vue
  10. 30 12
      Strides-Admin/src/views/site/SiteManagement.vue
  11. 5 5
      Strides-Admin/src/views/site/UpdateSite.vue
  12. 1 1
      Strides-Admin/src/views/site/components/Balancing.vue
  13. 266 0
      Strides-Admin/src/views/site/components/ChargeRate.vue
  14. 117 0
      Strides-Admin/src/views/site/components/Dashboard.vue
  15. 152 0
      Strides-Admin/src/views/site/components/EmailRecipient.vue
  16. 150 0
      Strides-Admin/src/views/site/components/Reservations.vue
  17. 244 0
      Strides-Admin/src/views/site/components/SiteTypeWithTime.vue
  18. 137 0
      Strides-Admin/src/views/site/components/Summary.vue
  19. 0 13
      Strides-Admin/src/views/site/components/UserWhitelistItem.vue
  20. 0 43
      Strides-Admin/src/views/site/components/UserWhitelistList.vue
  21. 130 0
      Strides-Admin/src/views/site/components/WhiteListFleet.vue
  22. 147 0
      Strides-Admin/src/views/site/components/WhiteListUser.vue
  23. 847 0
      Strides-Admin/src/views/site/detail.vue

+ 3 - 0
Strides-Admin/src/http/api/charge.js

@@ -33,6 +33,9 @@ const charge = {
   },
   getRegistrationStatusList: () => {
     return get('station/getRegistrationStatusList')
+  },
+  getStatusOptions: () => {
+    return get('base/getDataStatus')
   }
 }
 

+ 36 - 0
Strides-Admin/src/router/site/SiteManagementRouter.js → Strides-Admin/src/router/SiteRouter.js

@@ -37,6 +37,42 @@ export default {
         title: 'Add Site',
         activeMenu: '/site-management/index'
       }
+    },
+    {
+      path: '/site-management/edit/:id',
+      component: () => import('@/views/site/detail'),
+      hidden: true,
+      meta: {
+        title: 'Edit Site',
+        activeMenu: '/site-management/index'
+      },
+    },
+    {
+      path: '/site-management/add',
+      component: () => import('@/views/site/detail'),
+      hidden: true,
+      meta: {
+        title: 'Add Site',
+        activeMenu: '/site-management/index'
+      }
+    },
+    {
+      path: '/site-management/stations/:id',
+      component: () => import('@/views/charge/RegisteredChargeStations'),
+      hidden: true,
+      meta: {
+        title: 'Manage Chargers',
+        activeMenu: '/site-management/index'
+      }
+    },
+    {
+      path: '/site-management/connectors/:id',
+      component: () => import('@/views/charge/Connectors'),
+      hidden: true,
+      meta: {
+        title: 'Manage Connectors',
+        activeMenu: '/site-management/index'
+      }
     }
   ]
 }

+ 2 - 2
Strides-Admin/src/router/index.js

@@ -2,7 +2,7 @@ import Vue from 'vue'
 import VueRouter from 'vue-router'
 import Layout from '@/layout'
 
-import SiteManagementRouter from './site/SiteManagementRouter'
+import SiteRouter from './SiteRouter'
 import ChargeRouter from './ChargeRouter'
 import UserRouter from './UserRouter'
 import ProviderRouter from './ProviderRouter'
@@ -95,7 +95,7 @@ const constantRoutes = [
       }
     ],
   },
-  SiteManagementRouter,
+  SiteRouter,
   ChargeRouter,
   Transactions,
   {

+ 6 - 0
Strides-Admin/src/styles/index.scss

@@ -325,4 +325,10 @@ aside {
 .el-button.cancel-button:hover,
 .el-button.el-button--primary.cancel-button:hover {
   background: #e0e0e0;
+}
+
+.update-by {
+  flex: 1;
+  padding: 10px 0;
+  margin-left: 20px;
 }

+ 4 - 4
Strides-Admin/src/views/Administrator.vue

@@ -340,22 +340,22 @@
           "username": {
             required: true,
             trigger: 'blur',
-            message: 'Please type host'
+            message: 'Please type username'
           },
           "password": {
             required: true,
             trigger: 'blur',
-            message: 'Please type host'
+            message: 'Please type password'
           },
           "protocol": {
             required: true,
             trigger: 'blur',
-            message: 'Please type host'
+            message: 'Please type protocol'
           },
           "port": {
             required: true,
             trigger: 'blur',
-            message: 'Please type host'
+            message: 'Please type port'
           },
           "recipient": {
             pattern: /^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/,

+ 6 - 7
Strides-Admin/src/views/charge/AddStation.vue

@@ -38,7 +38,7 @@
               <el-form-item
                 label="Service Provider:"
                 prop="providerPk"
-                label-width="150px">
+                label-width="160px">
                 <el-select
                   class="add-text"
                   v-model="addForm.providerPk"
@@ -58,7 +58,7 @@
               <el-form-item
                 label="Site of Station:"
                 prop="sitePk"
-                label-width="150px">
+                label-width="160px">
                 <el-select
                   class="add-text"
                   filterable
@@ -79,7 +79,7 @@
             <el-col :xs="24" :md="24">
               <el-form-item
                 label="Additional Notes:"
-                label-width="150px">
+                label-width="160px">
                 <el-input
                   class="add-text"
                   type="textarea"
@@ -94,7 +94,7 @@
             <el-col :xs="24" :md="24">
               <el-form-item
                 label="Lot Number:"
-                label-width="150px">
+                label-width="160px">
                 <el-input
                   class="add-text"
                   maxlength="300"
@@ -107,7 +107,7 @@
             <el-col :xs="24" :md="24">
               <el-form-item
                 label="Level:"
-                label-width="150px">
+                label-width="160px">
                 <el-input
                   class="add-text"
                   maxlength="300"
@@ -310,8 +310,7 @@
           </el-button>
         </div>
         <div
-          class="flex1"
-          style="margin-left: 20px;"
+          class="update-by"
           v-if="isEdit">
           <span
             class="add-text"

+ 72 - 41
Strides-Admin/src/views/charge/Connectors.vue

@@ -4,47 +4,47 @@
       <el-form
         :model="filter"
         v-bind:inline="true"
-        label-position="left"
-        label-width="100px"
+        label-position="right"
+        label-width="90px"
         style="width: 100%;">
-        <el-row :gutter="20">
-          <el-col :xs="24" :md="12">
-            <el-form-item
-              label="Station ID:"
-              class="flex-item">
-              <el-input
-                v-model="filter.criteria"
-                placeholder="Search by Site Name,Station ID Service Provider"
-                @keyup.enter.native="handleFilter" />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="18" :sm="17" :md="7">
-            <el-form-item
-              label="Status:"
-              label-width="70px"
-              class="flex-item">
-              <el-select
-                v-model="filter.status"
-                placeholder="Select"
-                @change="handleFilter">
-                <el-option
-                  v-for="(item, index) in statusOptions"
-                  :key="index"
-                  :label="item.name"
-                  :value="item.value"/>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :xs="6" :sm="7" :md="5">
-            <el-button
-              v-waves
-              type="primary"
-              icon="el-icon-search"
-              @click="handleFilter">
-              Search
-            </el-button>
-          </el-col>
-        </el-row>
+        <div class="filter-view">
+          <div
+            v-if="sitePk"
+            class="back-icon"
+            @click="goBack">
+            <i class="el-icon el-icon-back"></i>
+          </div>
+          <el-form-item
+            label="Station ID:"
+            class="flex-item">
+            <el-input
+              v-model="filter.criteria"
+              placeholder="Search by Site Name,Station ID Service Provider"
+              @keyup.enter.native="handleFilter" />
+          </el-form-item>
+          <el-form-item
+            label="Status:"
+            class="flex-item"
+            label-width="70px">
+            <el-select
+              v-model="filter.status"
+              placeholder="Select"
+              @change="handleFilter">
+              <el-option
+                v-for="(item, index) in statusOptions"
+                :key="index"
+                :label="item.name"
+                :value="item.value"/>
+            </el-select>
+          </el-form-item>
+          <el-button
+            v-waves
+            type="primary"
+            icon="el-icon-search"
+            @click="handleFilter">
+            Search
+          </el-button>
+        </div>
       </el-form>
     </div>
     <el-table
@@ -170,13 +170,22 @@
         printConnector: {
           qrCode: "",
           visible: false
-        }
+        },
+        sitePk: ""
       }
     },
     created() {
+      if (this.$route.params.id) {
+        this.filter.sitePk = this.sitePk = this.$route.params.id;
+      }
       this.getStatusOption();
     },
     methods: {
+      goBack() {
+        this.$router.push({
+          path: "/site-management/edit/" + this.sitePk
+        })
+      },
       handleFilter() {
         this.listLoading = true;
         this.listQuery.page = 1;
@@ -239,4 +248,26 @@
       }
     }
   }
+  .filter-view {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+  }
+  .filter-view > * {
+    margin: 10px 5px;
+  }
+  .filter-flex-button {
+    flex: 1;
+    text-align: right;
+    margin-bottom: 0;
+  }
+  .back-icon {
+    width: 30px;
+    height: 30px;
+    padding: 10px;
+    display: flex;
+    cursor: pointer;
+    font-size: 18px;
+    align-items: center;
+  }
 </style>

+ 129 - 45
Strides-Admin/src/views/charge/RegisteredChargeStations.vue

@@ -4,47 +4,62 @@
       <el-form
         :model="filter"
         v-bind:inline="true"
-        label-position="left"
+        label-position="right"
         label-width="100px"
         style="width: 100%;">
-        <el-row :gutter="20">
-          <el-col :xs="18" :sm="12" :md="8">
-            <el-form-item
-              label=""
-              class="flex-item">
-              <el-input
-                v-model="filter.criteria"
-                placeholder="Search by Station ID/Site Name/Service Provider"
-                @keyup.enter.native="handleFilter" />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="6" :sm="4" :md="3">
-            <el-button
-              type="primary"
-              icon="el-icon-search"
-              @click="handleFilter">
-              Search
-            </el-button>
-          </el-col>
-          <el-col :xs="17" :sm="8" :md="6">
-            <el-form-item
-              label-width="130px"
-              label="Heartbeat Period:"
-              class="flex-item">
-              <el-select
-                v-model="filter.heartbeatPeriod"
-                placeholder="Select"
-                @change="handleFilter">
-                <el-option
-                  v-for="(item, index) in heartOptions"
-                  :key="index"
-                  :value="item.value"
-                  :label="item.name"/>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :xs="0" :sm="19" :md="2" :xl="3"><p></p></el-col>
-          <el-col :xs="7" :sm="5" :md="4">
+        <div class="filter-view">
+          <div
+            v-if="sitePk"
+            class="back-icon"
+            @click="goBack">
+            <i class="el-icon el-icon-back"></i>
+          </div>
+          <el-form-item
+            label=""
+            class="flex-item">
+            <el-input
+              v-model="filter.criteria"
+              placeholder="Search by Station ID/Site Name/Service Provider"
+              @keyup.enter.native="handleFilter" />
+          </el-form-item>
+          <el-button
+            type="primary"
+            icon="el-icon-search"
+            @click="handleFilter">
+            Search
+          </el-button>
+          <el-form-item
+            label-width="130px"
+            label="Heartbeat Period:"
+            class="flex-item">
+            <el-select
+              v-model="filter.heartbeatPeriod"
+              placeholder="Select"
+              @change="handleFilter">
+              <el-option
+                v-for="(item, index) in heartOptions"
+                :key="index"
+                :value="item.value"
+                :label="item.name"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label-width="130px"
+            label="Station Status:"
+            class="flex-item"
+            v-if="sitePk">
+            <el-select
+              v-model="filter.dataStatus"
+              placeholder="Select"
+              @change="handleFilter">
+              <el-option
+                v-for="(item, index) in statusOptions"
+                :key="index"
+                :value="item.value"
+                :label="item.name"/>
+            </el-select>
+          </el-form-item>
+          <div class="filter-flex-button">
             <el-button
               v-if="!$route.meta.onlyView"
               class="filter-item"
@@ -53,8 +68,8 @@
               @click="addStation">
                 Add Station
             </el-button>
-          </el-col>
-        </el-row>
+          </div>
+        </div>
       </el-form>
     </div>
     <el-table
@@ -80,7 +95,6 @@
       </el-table-column>
       <el-table-column
         label="Service Provider"
-        prop="sitePk"
         align="center"
         class-name="fixed-width">
           <template slot-scope="{row}">
@@ -95,6 +109,28 @@
             <span>{{ row.lastHeartbeat }}</span>
           </template>
       </el-table-column>
+      <el-table-column
+        label="Connector"
+        align="center"
+        width="100px"
+        class-name="fixed-width">
+        <template slot="header" slot-scope="scope">
+          <div>Connector</div>
+          <div style="font-size: 0.8em; margin-top: -5px;">(In Use/Total)</div>
+        </template>
+          <template slot-scope="{row}">
+            <span>{{ "0/0" }}</span>
+          </template>
+      </el-table-column>
+      <!--el-table-column
+        label="Status"
+        align="center"
+        width="120px"
+        class-name="fixed-width">
+          <template slot-scope="{row}">
+            <span>{{ "Active" }}</span>
+          </template>
+      </el-table-column-->
       <el-table-column
         v-if="!$route.meta.onlyView"
         label="Action"
@@ -128,23 +164,35 @@ export default {
     return {
       filter: {
         criteria: '',
+        dataStatus: '',
         heartbeatPeriod: 'ALL'
       },
       listLoading: true,
       stationList: [],
       heartOptions: [],
+      statusOptions: [],
       total: 1,
       listQuery: {
         page: 1,
         limit: 10,
       },
+      sitePk: ""
     }
   },
   created() {
+    if (this.$route.params.id) {
+      this.filter.sitePk = this.sitePk = this.$route.params.id;
+      this.getStatusOptions();
+    }
     this.getHeartbeatOptions();
     this.getList();
   },
   methods: {
+    goBack() {
+      this.$router.push({
+        path: "/site-management/edit/" + this.sitePk
+      })
+    },
     handleFilter() {
       this.listLoading = true;
       this.listQuery.page = 1;
@@ -162,6 +210,18 @@ export default {
         console.error('data', err);
       });
     },
+    getStatusOptions() {
+      api.getStatusOptions().then(res => {
+        console.log('data', res);
+        this.statusOptions = res.data;
+        this.statusOptions.unshift({
+          name: "All",
+          value: ""
+        })
+      }).catch(err => {
+        console.error('data', err);
+      });
+    },
     getList() {
       const params = {
         pageSize: this.listQuery.limit,
@@ -181,12 +241,12 @@ export default {
       });
     },
     addStation() {
-      this.$router.push('add-charge-stations');
+      this.$router.push('/charge/add-charge-stations');
     },
     editStation(row) {
       //console.log('row', row);
       this.$store.commit('charge/SET_StationId', row.chargeBoxPk)
-      this.$router.push('edit-charge-stations');
+      this.$router.push('/charge/edit-charge-stations');
     },
     delStation(row) {
       this.$confirm('Are you sure you want to delete this station ?', 'Delete', {
@@ -214,4 +274,28 @@ export default {
     }
   }
 }
-</script>
+</script>
+<style>
+  .filter-view {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+  }
+  .filter-view > * {
+    margin: 10px 5px;
+  }
+  .filter-flex-button {
+    flex: 1;
+    text-align: right;
+    margin-bottom: 0;
+  }
+  .back-icon {
+    width: 30px;
+    height: 30px;
+    padding: 10px;
+    display: flex;
+    cursor: pointer;
+    font-size: 18px;
+    align-items: center;
+  }
+</style>

+ 1 - 3
Strides-Admin/src/views/charging/EditProfile.vue

@@ -264,9 +264,7 @@
             Save
           </el-button>
         </div>
-        <div
-          class="flex1"
-          style="margin-left: 20px;">
+        <div class="update-by">
           <span
             class="add-text"
             :title='"CREATED BY " + editForm.createdBy + " ON " + editForm.createdOn'>

+ 30 - 12
Strides-Admin/src/views/site/SiteManagement.vue

@@ -71,36 +71,52 @@
         prop="siteAddress"
         align="center"
         min-width="280">
-          <template slot-scope="{row}">
-            <span>{{ row.siteAddress }}</span>
-          </template>
+        <template slot-scope="{row}">
+          <span>{{ row.siteAddress }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="Service Provider"
+        prop="siteName"
+        align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.serviceProvider }}</span>
+        </template>
       </el-table-column>
       <el-table-column
         label="Total Stations"
         prop="stationNo"
         align="center">
+          <template slot="header" slot-scope="scope">
+            <div>Stations</div>
+            <div style="font-size: 0.8em; margin-top: -5px;">(In Use/Total)</div>
+          </template>
           <template slot-scope="{row}">
-            <span>{{ row.stationNo }}</span>
+            <span>{{ row.stationInUse || "0" }}/{{ row.stationNo || "0" }}</span>
           </template>
       </el-table-column>
-      <el-table-column
+      <!--el-table-column
         label="Stations in Use"
         prop="stationNo"
         align="center">
           <template slot-scope="{row}">
             <span>{{ row.stationInUse || "0" }}</span>
           </template>
-      </el-table-column>
+      </el-table-column-->
       <el-table-column
         label="Total Connectors"
         prop="connectorNo"
         align="center"
         class-name="fixed-width">
+          <template slot="header" slot-scope="scope">
+            <div>Connectors</div>
+            <div style="font-size: 0.8em; margin-top: -5px;">(In Use/Total)</div>
+          </template>
           <template slot-scope="{row}">
-            <span>{{ row.connectorNo }}</span>
+            <span>{{ row.connectorInUse || "0" }}/{{ row.connectorNo || "0"}}</span>
           </template>
       </el-table-column>
-      <el-table-column
+      <!--el-table-column
         label="Connectors in Use"
         prop="connectorNo"
         align="center"
@@ -108,7 +124,7 @@
           <template slot-scope="{row}">
             <span>{{ row.connectorInUse || "0" }}</span>
           </template>
-      </el-table-column>
+      </el-table-column-->
       <el-table-column
         v-if="visible.actions"
         label="Action"
@@ -219,11 +235,13 @@ export default {
       this.getList()
     },
     handleClickAddSiteButton() {
-      this.$router.push({ path: '/site-management/add-site' })
+      //this.$router.push({ path: '/site-management/add-site' })
+      this.$router.push({ path: '/site-management/add' })
     },
     handleUpdateSite(row, index) {
-      this.$store.commit('site/SET_SELECTED_SITE', row)
-      this.$router.push({ path: '/site-management/update-site' })
+      /*this.$store.commit('site/SET_SELECTED_SITE', row)
+      this.$router.push({ path: '/site-management/update-site' })*/
+      this.$router.push({ path: '/site-management/edit/' + row.sitePk })
     },
     handleDeleteSite(row, index) {
       this.$confirm(

+ 5 - 5
Strides-Admin/src/views/site/UpdateSite.vue

@@ -18,11 +18,11 @@
             <el-form-item
               label="Site Name:"
               prop="siteInformation.siteName">
-                <el-input
-                  :disabled="isMCSTUser"
-                  v-model="updateSiteModel.siteInformation.siteName"
-                  class="input-text"
-                  @keyup.enter.native="handleFilter" />
+              <el-input
+                :disabled="isMCSTUser"
+                v-model="updateSiteModel.siteInformation.siteName"
+                class="input-text"
+                @keyup.enter.native="handleFilter" />
             </el-form-item>
             <el-form-item
               prop="siteInformation.latitude"

+ 1 - 1
Strides-Admin/src/views/site/components/Balancing.vue

@@ -102,7 +102,6 @@ export default {
       type: Object,
       required: true
     },
-    title: String,
     isEdit: {
       type: Boolean,
       default: false
@@ -189,6 +188,7 @@ export default {
         this.balancingForm.staticMaxAmpere = 0;
         this.balancingForm.siteChargingProfiles = [];
         this.handleAddSiteChargingProfiles()
+        this.onChange()
       }
     },
     /**

+ 266 - 0
Strides-Admin/src/views/site/components/ChargeRate.vue

@@ -0,0 +1,266 @@
+<template>
+  <div v-if="visible">
+    <div
+      v-for="(item, index) in chargeRates"
+      :key="index"
+      class="rate-list-view">
+      <el-form-item
+        label="Company:"
+        label-width="90px"
+        v-if="isSpecial">
+        <el-select
+          v-model="item.fleetCompanyId"
+          class="rate-text-max"
+          @change="onChange">
+          <el-option
+            v-for="item in companyOptions"
+            :key="item.fleetCompanyId"
+            :label="item.fleetCompanyName"
+            :value="item.fleetCompanyId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        label="Type:"
+        label-width="55px">
+        <el-select
+          v-model="item.chargeTypePk"
+          class="rate-text"
+          @change="onChange">
+          <el-option
+            v-for="item in chargeTypeOptions"
+            :key="item.chargeTypePk"
+            :label="item.chargeType"
+            :value="item.chargeTypePk">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        label="Rate:"
+        label-width="55px">
+        <el-input
+          v-model="item.rate"
+          class="rate-text"
+          @blur="onChange"/>
+      </el-form-item>
+      <div class="ratePunit">/</div>
+      <el-form-item
+        label=""
+        label-width="0px">
+        <el-select
+          v-model="item.rateType"
+          class="rate-type"
+          @change="onChange">
+          <el-option
+            v-for="item in rateTypeOptions"
+            :key="item.name"
+            :label="item.value"
+            :value="item.value"/>
+        </el-select>
+      </el-form-item>
+      <img
+        class="list-item-icon"
+        @click="handleClickSubButton(item, index)"
+        src="../../../assets/form-list-sub.png"/>
+      <img
+        v-if="chargeRates.length - 1 === index"
+        class="list-item-icon"
+        @click="handleClickAddButton"
+        src="../../../assets/form-list-add.png"/>
+    </div>
+  </div>
+</template>
+
+<script>
+import site from '@/http/api/site'
+import { fetchFleetCompanyOptions } from '@/http/api/driver'
+export default {
+  name: "ChargeRate",
+  props: {
+    visible: {
+      type: Boolean,
+      default: true
+    },
+    isSpecial: {
+      type: Boolean,
+      default: false
+    },
+    value: {
+      type: Array,
+      required: true
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  data() {
+    return {
+      chargeRates: [{
+        rate: '',
+        rateType: '',
+        chargeTypePk: ''
+      }],
+      companyOptions: [],
+      rateTypeOptions: [],
+      chargeTypeOptions: [],
+      tempChargeRate: {
+        rate: '',
+        rateType: '',
+        chargeTypePk: '',
+        //fleetCompanyId: ''
+      },
+      rules: {
+        chargeRate: {
+          fleetCompanyId: {
+            required: true,
+            trigger: 'change',
+            message: 'Fleet Company is required',
+          },
+          chargeTypePk: {
+            required: true,
+            trigger: 'change',
+            message: 'Charge Type is required',
+          },
+          rate: [{
+            required: true,
+            trigger: 'blur',
+            message: 'Rate is required',
+          }, {
+            pattern: /^\d+(\.\d+)?$/,
+            trigger: 'blur',
+            message: 'Please type a correct rate',
+          }],
+          rateType: {
+            required: true,
+            trigger: 'change',
+            message: 'Rate Type is required',
+          }
+        }
+      }
+    };
+  },
+  watch: {
+    value: {
+      deep: true,
+      handler(n, o) {
+        this.chargeRates = n;
+      }
+    }
+  },
+  mounted() {
+    this.getChargeTypeOptions()
+    this.getRateTypeOptions()
+    if (this.isSpecial) {
+      this.getCompanyOptions()
+    }
+  },
+  methods: {
+    onChange() {
+      this.$emit('change', this.chargeRates);
+    },
+    getRateTypeOptions() {
+      site.getRateTypeList().then(res => {
+        if (res.data) {
+          this.rateTypeOptions = res.data
+          if (res.data.length > 0) {
+            this.chargeRates[0].rateType = res.data[0].value
+            this.tempChargeRate.rateType = res.data[0].value
+            this.onChange()
+          }
+        }
+      });
+    },
+    getChargeTypeOptions() {
+      site.getChargeTypeList().then(res => {
+        if (res.data) {
+          this.chargeTypeOptions = res.data
+          if (res.data.length > 0) {
+            this.chargeRates[0].chargeTypePk = res.data[0].chargeTypePk
+            this.tempChargeRate.chargeTypePk = res.data[0].chargeTypePk
+            this.onChange()
+          }
+        }
+      });
+    },
+    getCompanyOptions() {
+      fetchFleetCompanyOptions().then(res => {
+        if (res.data) {
+          this.companyOptions = res.data
+          if (res.data.length > 0) {
+            this.chargeRates[0].fleetCompanyId = res.data[0].fleetCompanyId
+            this.tempChargeRate.fleetCompanyId = res.data[0].fleetCompanyId
+            this.onChange()
+          }
+        }
+      })
+    },
+    handleClickAddButton() {
+      this.chargeRates.push(JSON.parse(JSON.stringify(this.tempChargeRate)));
+      //this.onChange();
+    },
+    handleClickSubButton(item, index) {
+      if (item.ratePk) {
+        this.$confirm('Are you sure you want to delete this charge rate ?', 'Delete', {
+          confirmButtonText: 'Ok',
+          cancelButtonText: 'Cancel',
+          type: 'warning',
+        }).then(() => {
+          this.deleteChargeRate(item, index);
+        })
+      } else {
+        this.chargeRates.splice(index, 1);
+        if (this.chargeRates.length == 0) {
+          this.handleClickAddButton()
+        }
+      }
+    },
+    deleteChargeRate(item, index) {
+      site.deleteChargeRate({ ratePk: item.ratePk }).then(() => {
+        this.chargeRates.splice(index, 1);
+        if (this.chargeRates.length == 0) {
+          this.handleClickAddButton()
+        }
+      }).catch((error) => {
+        this.$notify({
+          title: 'Delete charge rate failed',
+          message: error,
+          type: 'error',
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rate-list-view {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.list-item-icon {
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  margin: 0 10px 22px;
+}
+.ratePunit {
+  height: 39px;
+  font-size: 20px;
+  margin-left: -5px;
+  padding-right: 10px;
+}
+.rate-text {
+  max-width: 150px;
+  padding-right: 14px;
+}
+.rate-type {
+  max-width: 100px;
+  padding-right: 14px;
+}
+.rate-text-max {
+  max-width: 200px;
+  padding-right: 14px;
+}
+</style>

+ 117 - 0
Strides-Admin/src/views/site/components/Dashboard.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="flexcr dashboard">
+    <div class="dashboard-item" v-for="(item, index) in dashboard" :key="index">
+      <div class="item-value">{{info[item.key] || "0"}}</div>
+      <div class="item-title">{{item.title}}</div>
+      <el-button
+        type="primary"
+        class="item-button"
+        :disabled="!enable"
+        @click="item.onClick">
+        {{item.button}}
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Dashboard",
+    props: {
+      siteId: String|Number,
+      info: {
+        type: Object,
+        default: {}
+      },
+      enable: {
+        type: Boolean,
+        default: false
+      }
+    },
+    data() {
+      return {
+        siteInfo: {},
+        dashboard: [{
+          key: "stations",
+          title: "Stations",
+          button: "Configure Station",
+          onClick: () => this.toStations()
+        },{
+          key: "connectors",
+          title: "Connectors",
+          button: "View Connectors",
+          onClick: () => this.toConnectors()
+        },{
+          key: "activeTransaction",
+          title: "Active Transaction",
+          button: "View Transactions",
+          onClick: () => this.toTransactions()
+        },{
+          key: "reservationsMade",
+          title: "Reservations Made",
+          button: "View Reservations",
+          onClick: () => this.toReservations()
+        }]
+      };
+    },
+    mounted() {
+      
+    },
+    methods: {
+      toStations() {
+        this.$router.push({
+          path: "/site-management/stations/" + this.siteId
+        })
+      },
+      toConnectors() {
+        this.$router.push({
+          path: "/site-management/connectors/" + this.siteId
+        })
+      },
+      toTransactions() {
+        this.$router.push({
+          path: "/transactions-reservations/transactions"
+        })
+      },
+      toReservations() {
+        this.$router.push({
+          path: "/transactions-reservations/reservations"
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+.dashboard {
+  zoom: 0.88;
+}
+.dashboard-item {
+  flex: 1;
+  display: flex;
+  margin: 5px 8px;
+  align-items: center;
+  flex-direction: column;
+}
+.item-value {
+  color: #333;
+  font-size: 22px;
+  font-weight: bold;
+  line-height: 30px;
+}
+.item-title {
+  color: #333;
+  font-size: 14px;
+  line-height: 20px;
+  white-space: nowrap;
+  text-transform: uppercase;
+}
+.item-button {
+  width: 100%;
+  max-width: 200px;
+  font-size: 13px;
+  margin-top: 10px;
+  padding: 12px 10px;
+  text-transform: uppercase;
+}
+</style>

+ 152 - 0
Strides-Admin/src/views/site/components/EmailRecipient.vue

@@ -0,0 +1,152 @@
+<template>
+  <div>
+    <el-form
+      :model="formData"
+      :rules="rule"
+      ref="recipForm"
+      label-position="left"
+      label-width="150px">
+      <el-form-item
+        label="Recipients:"
+        prop="recipient"
+        label-width="100px">
+        <div class="flexcr" style="margin-top: -5px;">
+          <el-input
+            class="input-text"
+            v-model="formData.recipient"
+            placeholder="Add text"
+            maxlength="50"/>
+          <el-button
+            type="primary"
+            icon="el-icon-plus"
+            @click="addRecipient">
+            Add
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item
+        label="Added Recipients:">
+        <div class="flexl">
+          <div class="receip-item" v-for="(item, index) in emails" :key="index">
+            <span>{{item}}</span>
+            <i class="el-icon-close" @click="deleteRecipient(index)"></i>
+          </div>
+          <div v-if="emails.length > 0">
+            <span class="link-button" @click="clearAllRecipient">Clear All</span>
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "EmailRecipient",
+    props: {
+      value: {
+        type: Array,
+        default: []
+      }
+    },
+    data() {
+      return {
+        emails: [],
+        formData: {
+          recipient: ""
+        },
+        rule: {
+          "recipient": {
+            pattern: /^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/,
+            trigger: 'blur',
+            message: 'Please type a correct recipient'
+          }
+        }
+      };
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    },
+    watch: {
+      value: {
+        deep: true,
+        handler(n, o) {
+          if (!n) {
+            this.emails = [];
+            this.onChange()
+          } else {
+            this.emails = n;
+          }
+        }
+      }
+    },
+    methods: {
+      onChange() {
+        this.$emit('change', this.emails);
+      },
+      addRecipient() {
+        if (this.formData.recipient) {
+          this.$refs["recipForm"].validate(result => {
+            if (result) {
+              this.emails.push(this.formData.recipient);
+              this.formData.recipient = "";
+              this.onChange();
+            }
+          });
+        }
+      },
+      deleteRecipient(index) {
+        this.emails.splice(index, 1);
+        this.onChange();
+      },
+      clearAllRecipient() {
+        this.$confirm('Clear all recipients?', 'Prompt', {
+          confirmButtonText: 'Confirm',
+          cancelButtonText: 'Cancel',
+          type: 'warning'
+        }).then(res => {
+          this.emails = [];
+          this.onChange();
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .input-text {
+    width: 100%;
+    max-width: 200px;
+    margin-top: 5px;
+    margin-right: 10px;
+    margin-bottom: 5px;
+  }
+  .link-button {
+    color: #3179E4;
+    font-size: 14px;
+    cursor: pointer;
+    margin: 0 -10px;
+    padding: 0 10px;
+    display: inline-block;
+    white-space: nowrap;
+    text-decoration: underline;
+  }
+  .receip-item {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    line-height: 1em;
+    padding-top: 10px;
+  }
+  .receip-item i {
+    color: #666;
+    padding: 0 10px;
+    cursor: pointer;
+  }
+  .receip-item i:hover,
+  .receip-item i:active,
+  .link-button:hover {
+    color: #ff5500;
+  }
+</style>

+ 150 - 0
Strides-Admin/src/views/site/components/Reservations.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="reservation-container">
+    <el-form-item
+      label-width="155px"
+      style="height: 40px; margin-right: 40px"
+      label="Enable Reservation"
+      :rules="[{ required: true,  trigger: 'blur'}]">
+      <el-switch
+        :value="enable"
+        @change="enableChange" />
+    </el-form-item>
+    <el-form-item
+      label-width="100px"
+      :rules="[{ required: true,  trigger: 'blur'}]"
+      label="Time Limit">
+      <el-select
+        :disabled="!enable"
+        class="reservation-time-limit-select"
+        :value="timeLimit"
+        @change="limitTimeChange"
+        placeholder="">
+        <el-option 
+          v-for="option in reservationTimeLimitOptions"
+          :label="option.lable"
+          :value="option.value"
+          :key="option.lable"
+        />
+      </el-select>
+      <span class="reservation-time-limit-unit">
+        Minutes
+      </span>
+    </el-form-item>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Reservation',
+  props: {
+    enabled: {
+      type: Boolean,
+      default: true,
+    },
+    limit: Number
+  },
+  data() {
+    return {
+      enable: this.enabled,
+      timeLimit: this.limit || 15,
+      reservationTimeLimitOptions: [{
+        value: 15,
+        lable: "15"
+      },{
+        value: 30,
+        lable: "30"
+      },{
+        value: 45,
+        lable: "45"
+      },{
+        value: 60,
+        lable: "60"
+      }],
+    }
+  },
+  watch: {
+    enabled(n, o) {
+      this.enable = n;
+    },
+    enable(n, o) {
+      this.$emit('update:enabled', n)
+    },
+    limit(n, o) {
+      this.timeLimit = n || 15;
+    },
+    timeLimit(n, o) {
+      this.$emit('update:limit', n)
+    }
+  },
+  mounted() {
+    if (!this.limit) {
+      this.$emit('update:limit', this.timeLimit);
+    }
+  },
+  methods: {
+    change() {
+      this.$emit('change', {
+        enable: this.enable,
+        timeLimit: this.timeLimit
+      })
+    },
+    limitTimeChange (limitTime) {
+      this.timeLimit = limitTime;
+      this.$emit('time-limit-change', limitTime)
+      this.change();
+    },
+    enableChange (enable) {
+      this.enable = enable;
+      this.$emit('enable', enable);
+      this.change();
+    }
+  },
+}
+</script>
+
+<style scoped lang='scss'>
+
+  .sparator {
+    margin: 10px -40px;
+    height: 2px;
+    background-color: #F0F5FC;
+  }
+
+  .section-title {
+    color: #333333;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 16px;
+    line-height: 24px;
+    font-weight: 500;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+
+  .reservation-container {
+    display: flex;
+    align-items: center;
+  }
+
+  .reservation-time-limit-select {
+    width: 134px;
+  }
+
+  .reservation-time-limit-unit {
+    font-family: sans-serif;
+    font-style: normal;
+    font-weight: 500;
+    font-size: 14px;
+    line-height: 21px;
+    text-transform: capitalize;
+    color: #434343;
+    padding-left: 10px;
+  }
+
+  @media screen and (max-width: 851px) {
+    .reservation-container {
+      flex-wrap: wrap;
+    }
+  }
+
+</style>

+ 244 - 0
Strides-Admin/src/views/site/components/SiteTypeWithTime.vue

@@ -0,0 +1,244 @@
+<template>
+  <div>
+  <el-table
+    :data="siteTypeTable"
+    class="type-table"
+    header-row-class-name="customer-row"
+    row-class-name="customer-row">
+    <el-table-column
+      label="Site Type With Schedule"
+      prop="label"
+      width="200px">
+      <template slot="header">
+        <div>
+          <span class="require">*</span>
+          Site Type With Schedule
+        </div>
+      </template>
+      <template slot-scope="{row}">
+        <span>{{ row.label }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="All Day"
+      width="80px">
+      <template slot="header">
+        <div>
+          <span class="require">*</span>
+          All Day
+        </div>
+      </template>
+      <template slot-scope="{row, $index}">
+        <el-switch
+          v-model="row.allDay"
+          :disabled="isSiteTypeEnable($index, true)"
+          @change="changeAllDay"/>
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="Start Time">
+      <template slot="header">
+        <div>
+          <span class="require1">*</span>
+          Start Time
+        </div>
+      </template>
+      <template slot-scope="{row, $index}">
+        <el-time-picker
+          class="time-picker"
+          v-model="row.startTime"
+          format="HH:mm"
+          value-format="HH:mm"
+          placeholder="Start Time"
+          :disabled="isSiteTypeEnable($index)">
+        </el-time-picker>
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="End Time">
+      <template slot="header">
+        <div>
+          <span class="require1">*</span>
+          End Time
+        </div>
+      </template>
+      <template slot-scope="{row, $index}">
+        <el-time-picker
+          class="time-picker"
+          v-model="row.endTime"
+          format="HH:mm"
+          value-format="HH:mm"
+          placeholder="End Time"
+          :disabled="isSiteTypeEnable($index)">
+        </el-time-picker>
+      </template>
+    </el-table-column>
+  </el-table>
+  </div>
+</template>
+
+<script>
+import site from '@/http/api/site'
+export default {
+  name: "",
+  props: {
+    siteTypes: {
+      type: Array,
+      default: []
+    }
+  },
+  data() {
+    return {
+      isInit: false,
+      siteTypeTable: [{
+        siteType: "",
+        allDay: false,
+        startTime: "",
+        endTime: ""
+      }]
+    };
+  },
+  watch: {
+    siteTypes: {
+      deep: true,
+      handler(n, o) {
+        if (n && n.length) {
+          this.init();
+        }
+      }
+    },
+    siteTypeTable: {
+      deep: true,
+      handler(n, o) {
+        if (this.isInit) {
+          this.isInit = false;
+          console.log("跳过onChange");
+        } else {
+          this.onChange();
+        }
+      }
+    }
+  },
+  mounted() {
+    this.getSiteTypeOptions();
+  },
+  methods: {
+    onChange() {
+      /*if (this.isAnyAllDay()) {
+        this.siteTypeTable.forEach(item => {
+          item.startTime = "";
+          item.endTime = "";
+        })
+      }
+      console.log("onChange");*/
+      this.$emit('change', this.siteTypeTable);
+    },
+    init() {
+      this.isInit = true;
+      const types = []
+      for (let item of this.siteTypeTable) {
+        for (let t of this.siteTypes) {
+          if (item.siteType == t.siteType) {
+            types.push({
+              label: item.label,
+              siteType: item.siteType,
+              allDay: t.allDay,
+              startTime: t.startTime,
+              endTime: t.endTime
+            })
+            break;
+          }
+        }
+      }
+      this.siteTypeTable = types;
+    },
+    changeAllDay(allDay) {
+      if (allDay) {
+        this.siteTypeTable.forEach(item => {
+          item.startTime = "";
+          item.endTime = "";
+        })
+      }
+    },
+    getSiteTypeOptions() {
+      site.getSiteTypeList().then(res => {
+        if (res.data && res.data.length) {
+          this.siteTypeTable = [];
+          res.data.forEach(item => {
+            this.siteTypeTable.push({
+              label: item.name,
+              siteType: item.value,
+              allDay: false,
+              startTime: "",
+              endTime: ""
+            })
+          })
+          if (this.siteTypes && this.siteTypes.length) {
+            this.init();
+          }
+        }
+      })
+    },
+    isAnyAllDay() {
+      for (let i = 0; i < this.siteTypeTable.length; i++) {
+        let type = this.siteTypeTable[i];
+        if (type.allDay) {
+          return true;
+        } else {
+          continue;
+        }
+      }
+      return false;
+    },
+    /**判断是否应该启用组件
+     * @param {Object} index 索引
+     * @param {Object} isSwitch 是否是Switch组件
+     */
+    isSiteTypeEnable(index, isSwitch) {
+      for (let i = 0; i < this.siteTypeTable.length; i++) {
+        let type = this.siteTypeTable[i];
+        if (type.allDay) {
+          if (index == i && isSwitch) {
+            return false;
+          } else {
+            return true;
+          }
+        } else {
+          continue;
+        }
+      }
+      return false;
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+  .type-table {
+    width: 100%;
+    min-width: 520px;
+    max-width: 800px;
+    padding-bottom: 10px;
+  }
+  .type-table:before {
+    height: 0;
+  }
+  .require {
+    color: #f56c6c;
+    font-size: 14px;
+    margin-left: -8px;
+  }
+  .require1 {
+    color: #f56c6c;
+    font-size: 14px;
+  }
+  ::v-deep .customer-row {
+    th,td {
+      background: #fff;
+      border-bottom: none;
+    }
+    .time-picker {
+      width: 100%;
+    }
+  }
+</style>

+ 137 - 0
Strides-Admin/src/views/site/components/Summary.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="summary-layout">
+    <div v-if="info.connectorStatus">
+      <div class="summary-view">
+        <div class="summary-text">Total Number Of Stations</div>
+        <div class="summary-text">{{info.connectors || "0"}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Available</div>
+        <div class="summary-text">{{info.connectorStatus.available}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">In use</div>
+        <div class="summary-text">{{info.connectorStatus.inUse}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Reserved</div>
+        <div class="summary-text">{{info.connectorStatus.reserved}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Faulted</div>
+        <div class="summary-text">{{info.connectorStatus.faulted}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Unavailable</div>
+        <div class="summary-text">{{info.connectorStatus.unavailable}}</div>
+      </div>
+    </div>
+    
+    <div v-if="info.consumption">
+      <div class="summary-view">
+        <div class="summary-text">Total Consumption 2023</div>
+        <div class="summary-text">{{info.consumption.thisYearTotalConsumption}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Today’s Consumption</div>
+        <div class="summary-text">{{info.consumption.thisDayTotalConsumption}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Highest Delivered</div>
+        <div class="summary-text">{{info.consumption.thisDayHighestDelivered}}</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Lowest Delivered</div>
+        <div class="summary-text">{{info.consumption.thisDayLowestDelivered}}</div>
+      </div>
+    </div>
+    
+    <div v-if="info.revenue">
+      <div class="summary-view">
+        <div class="summary-text">Total Revenue 2023</div>
+        <div class="summary-text">{{currency}}1900</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Transaction Completed</div>
+        <div class="summary-text">12</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Transactions Ongoing</div>
+        <div class="summary-text">12</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Highest transaction</div>
+        <div class="summary-text">{{currency}}20</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Lowest Transaction</div>
+        <div class="summary-text">{{currency}}65</div>
+      </div>
+      <div class="summary-sub-view">
+        <div class="summary-text">Average Transaction</div>
+        <div class="summary-text">{{currency}}20</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Summary",
+    props: {
+      info: {
+        type: Object,
+        default: {}
+      },
+      currency: {
+        type: String,
+        default: "S$"
+      }
+    },
+    data() {
+      return {
+        
+      };
+    },
+    mounted() {
+      
+    },
+    methods: {
+      
+    }
+  }
+</script>
+
+<style scoped>
+  .summary-layout {
+    margin-top: -15px;
+    padding-bottom: 10px;
+  }
+  .summary-view {
+    margin-top: 5px;
+  }
+  .summary-view,
+  .summary-sub-view {
+    display: flex;
+    padding: 10px 0;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .summary-sub-view {
+    padding: 2px 0;
+    padding-left: 40px;
+  }
+  .summary-sub-view:before {
+    width: 4px;
+    height: 4px;
+    content: " ";
+    border-radius: 5px;
+    position: absolute;
+    background: #333;
+    margin-left: -10px;
+  }
+  .summary-text {
+    color: #333;
+    font-size: 14px;
+  }
+</style>

+ 0 - 13
Strides-Admin/src/views/site/components/UserWhitelistItem.vue

@@ -1,13 +0,0 @@
-<template>
-  
-</template>
-
-<script>
-export default {
-  name: 'UserWhitelistItem',
-}
-</script>
-
-<style>
-
-</style>

+ 0 - 43
Strides-Admin/src/views/site/components/UserWhitelistList.vue

@@ -1,43 +0,0 @@
-<template>
-  <div>
-    <div
-      style="display: flex; flex-direction: row;"
-      v-for="(item, index) in siteForm.whitelistUser"
-      :key="`${item.whitelistPk}-${item.sitePk}`">
-      <el-form-item
-        label="Mobile Number"
-        prop="type">
-          <el-input
-            v-model="item.mobileNumber"
-            style="width: 254px;"
-            class="filter-item"
-            placeholder="Please enter rate" />
-      </el-form-item>
-      <el-form-item
-        label="Licence Number"
-        prop="type">
-          <el-input
-            v-model="item.licenceNumber"
-            style="width: 254px;"
-            class="filter-item"
-            placeholder="Please enter rate" />
-      </el-form-item>
-      <el-button
-        class="filter-item"
-        type="primary"
-        @click="handleClickSubWhiteUserButton(item, index)">
-          - 
-      </el-button>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'UserWhitelistList',
-}
-</script>
-
-<style>
-
-</style>

+ 130 - 0
Strides-Admin/src/views/site/components/WhiteListFleet.vue

@@ -0,0 +1,130 @@
+<template>
+  <div>
+    <div
+      class="rate-list-view"
+      v-for="(item, index) in fleetWhitelist"
+      :key="index">
+      <el-form-item
+        label-width="50px"
+        label="Fleet">
+        <el-select
+          v-model="item.fleetCompanyId"
+          style="margin-right: 10px;">
+          <el-option
+            v-for="item in companyOptions"
+            :key="item.fleetCompanyId"
+            :label="item.fleetCompanyName"
+            :value="item.fleetCompanyId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <img
+        class="list-item-icon"
+        @click="handleClickSubWhiteFleetButton(item, index)"
+        src="../../../assets/form-list-sub.png"/>
+      <img
+        v-if="index === fleetWhitelist.length - 1"
+        class="list-item-icon"
+        @click="handleClickAddWhiteFleetButton"
+        src="../../../assets/form-list-add.png"/>
+    </div>
+  </div>
+</template>
+
+<script>
+import { fetchFleetCompanyOptions } from '@/http/api/driver'
+export default {
+  name: 'WhiteListFleet',
+  props: {
+    value: {
+      type: Array,
+      required: true
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  data() {
+    return{
+      fleetWhitelist: [{
+        //siteFleetId: "",
+        fleetCompanyId: ''
+      }],
+      companyOptions: []
+    }
+  },
+  watch: {
+    value: {
+      deep: true,
+      handler(n, o) {
+        this.fleetWhitelist = n;
+      }
+    }
+  },
+  mounted() {
+    this.getCompanyOptions()
+  },
+  methods: {
+    onChange() {
+      this.$emit('change', this.fleetWhitelist);
+    },
+    getCompanyOptions() {
+      fetchFleetCompanyOptions().then(res => {
+        if (res.data) {
+          this.companyOptions = res.data
+        }
+      })
+    },
+    handleClickAddWhiteFleetButton() {
+      this.fleetWhitelist.push({
+        fleetCompanyId: ''
+      })
+    },
+    handleClickSubWhiteFleetButton(fleet, index) {
+      if (fleet.siteFleetId) {
+        this.$confirm('Are you sure you want to delete this fleet?', 'Delete', {
+          confirmButtonText: 'Ok',
+          cancelButtonText: 'Cancel',
+          type: 'warning',
+        }).then(() => {
+          this.deleteWhiteFleet(fleet, index);
+        })
+      } else {
+        this.fleetWhitelist.splice(index, 1)
+        if (this.fleetWhitelist.length === 0) {
+          this.handleClickAddWhiteFleetButton()
+        }
+      }
+    },
+    deleteWhiteFleet(fleet, index) {
+      site.deleteWhitelistFleet({siteFleetId: fleet.siteFleetId}).then(res => {
+        this.fleetWhitelist.splice(index, 1)
+        if (this.fleetWhitelist.length === 0) {
+          this.handleClickAddWhiteFleetButton()
+        }
+      }).catch(err => {
+        this.$notify({
+          type: 'error',
+          message: error,
+          title: 'Delete fleet failed'
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rate-list-view {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.list-item-icon {
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  margin: 0 10px 22px;
+}
+</style>

+ 147 - 0
Strides-Admin/src/views/site/components/WhiteListUser.vue

@@ -0,0 +1,147 @@
+<template>
+  <div>
+    <div
+      class="rate-list-view"
+      v-for="(item, index) in whitelistUser"
+      :key="index">
+      <el-form-item
+        label-width="130px"
+        label="Mobile Number"
+        :rules="rules.mobileNumber">
+        <el-input
+          v-model="item.mobileNumber"
+          class="rate-text" />
+        <!-- <div class="el-form-item__error">Please type mobile number</div> -->
+      </el-form-item>
+      <el-form-item
+        label-width="140px"
+        label="Licence Number"
+        :rules="rules.licenceNumber">
+        <el-input
+          v-model="item.licenceNumber"
+          class="rate-text" />
+        <!-- <div class="el-form-item__error">Please type licence number</div> -->
+      </el-form-item>
+      <img
+        class="list-item-icon"
+        @click="handleClickSubWhiteUserButton(item, index)"
+        src="../../../assets/form-list-sub.png"/>
+      <img
+        v-if="index === whitelistUser.length - 1"
+        class="list-item-icon"
+        @click="handleClickAddWhiteUserButton"
+        src="../../../assets/form-list-add.png"/>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'WhiteListUser',
+  props: {
+    value: {
+      type: Array,
+      required: true
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  data() {
+    return {
+      whitelistUser: [{
+        licenceNumber: '',
+        mobileNumber: '',
+      }],
+      rules: {
+        mobileNumber: {
+          
+        },
+        licenceNumber: {
+          
+        },
+        whitelistUser: {
+          licenceNumber: {
+            required: true,
+            trigger: 'blur',
+            message: 'Licence Number is required',
+          },
+          mobileNumber: {
+            required: true,
+            trigger: 'blur',
+            message: 'Mobile Number is required',
+          }
+        }
+      }
+    }
+  },
+  watch: {
+    value: {
+      deep: true,
+      handler(n, o) {
+        this.whitelistUser = n;
+      }
+    }
+  },
+  methods: {
+    onChange() {
+      this.$emit('change', this.whitelistUser);
+    },
+    handleClickAddWhiteUserButton() {
+      this.whitelistUser.push({
+        mobileNumber: '',
+        licenceNumber: '',
+      })
+    },
+    handleClickSubWhiteUserButton(user, index) {
+      if (user.whitelistPk) {
+        this.$confirm('Are you sure you want to delete this user?', 'Delete', {
+          confirmButtonText: 'Ok',
+          cancelButtonText: 'Cancel',
+          type: 'warning',
+        }).then(() => {
+          this.deleteWhiteUser(user, index);
+        });
+      } else {
+        this.whitelistUser.splice(index, 1)
+        if (this.whitelistUser.length === 0) {
+          this.handleClickAddWhiteUserButton();
+        }
+      }
+    },
+    deleteWhiteUser(user, index) {
+      site.deleteWhitelistUser({whitelistPk: user.whitelistPk}).then(() => {
+        this.whitelistUser.splice(index, 1)
+        if (this.whitelistUser.length === 0) {
+          this.handleClickAddWhiteUserButton();
+        }
+      }).catch((error) => {
+        this.$notify({
+          type: 'error',
+          message: error,
+          title: 'Delete user failed'
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rate-list-view {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.rate-text {
+  max-width: 150px;
+  padding-right: 14px;
+}
+.list-item-icon {
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  margin: 0 10px 22px;
+}
+</style>

+ 847 - 0
Strides-Admin/src/views/site/detail.vue

@@ -0,0 +1,847 @@
+<template>
+  <div class="view-container" v-loading="pageLoading">
+    <el-form
+      ref="addForm"
+      :rules="rules"
+      :model="siteForm"
+      label-position="left"
+      label-width="120px"
+      style="width: 100%;">
+      <div class="flexr">
+        <div class="flexl flex1">
+          <div class="view-content flex1">
+            <div class="section-title" id="idSiteInfo">Site Information</div>
+            <el-form-item
+              label="Site Name:"
+              prop="siteName">
+              <el-input
+                :disabled="isMCSTUser"
+                v-model="siteForm.siteName"
+                class="input-text" />
+            </el-form-item>
+            <el-form-item
+              prop="locationLatitude"
+              label="Latitude:">
+              <el-input
+                :disabled="isMCSTUser"
+                v-model="siteForm.locationLatitude"
+                class="input-text" />
+            </el-form-item>
+            <el-form-item
+              prop="locationLongitude"
+              label="Longitude:">
+              <el-input
+                :disabled="isMCSTUser"
+                v-model="siteForm.locationLongitude"
+                class="input-text"/>
+            </el-form-item>
+            <el-form-item
+              prop="address.countryCode"
+              label="Country:">
+              <el-select
+                v-model="siteForm.address.countryCode"
+                class="input-text"
+                :disabled="isMCSTUser">
+                <el-option
+                  v-for="item in countryOptions"
+                  :key="item.name"
+                  :label="item.name"
+                  :value="item.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              prop="address.city"
+              label="City:">
+              <el-input
+                v-model="siteForm.address.city"
+                class="input-text"
+                :disabled="isMCSTUser"/>
+            </el-form-item>
+            <el-form-item
+              prop="address.street"
+              label="Street:">
+              <el-input
+                v-model="siteForm.address.street"
+                class="input-text"
+                :disabled="isMCSTUser"/>
+            </el-form-item>
+            <el-form-item
+              prop="address.zipCode"
+              label="Postal Code:">
+              <el-input
+                v-model="siteForm.address.zipCode"
+                class="input-text"
+                :disabled="isMCSTUser"/>
+            </el-form-item>
+          </div>
+          <div class="view-content">
+            <div class="section-title">Additional Information</div>
+            <el-form-item
+              label-width="130px"
+              label="Operating Hours:"
+              class="flex-item">
+              <div class="flexc">
+                <el-input
+                  v-model="siteForm.operatingHours"
+                  :disabled="isMCSTUser || !!siteForm.endlessService"
+                  class="input-text" />
+                <el-switch
+                  :disabled="isMCSTUser"
+                  @change="changeEndless"
+                  style="margin-left: 10px"
+                  inactive-text="24/7"
+                  v-model="siteForm.endlessService">
+                </el-switch>
+              </div>
+            </el-form-item>
+            <el-form-item
+              label-width="130px"
+              label="Parking Fee:"
+              class="flex-item">
+              <div class="flexc">
+                <el-input
+                  :disabled="isMCSTUser || !!siteForm.free"
+                  v-model="siteForm.parkingFee"
+                  class="input-text" />
+                <el-switch
+                  :disabled="isMCSTUser"
+                  @change="changeParkingFree"
+                  style="margin-left: 10px"
+                  inactive-text="Free"
+                  v-model="siteForm.free">
+                </el-switch>
+              </div>
+            </el-form-item>
+            <el-form-item
+              label-width="130px"
+              label="Additional Notes:">
+              <el-input
+                :disabled="isMCSTUser"
+                v-model="siteForm.additionalNotes"
+                type="textarea"
+                :autosize="autosize"
+                class="input-text" />
+            </el-form-item>
+            <el-form-item
+              label-width="130px"
+              label="Carpark Code:">
+              <el-input
+                v-model="siteForm.carParkCode"
+                class="input-text" />
+            </el-form-item>
+          </div>
+        </div>
+        <div class="flexl flex1">
+          <div class="view-content" style="padding: 15px;">
+            <dashboard
+              :enable="isEdit"
+              :info="summaryInfo"
+              :siteId="siteForm.sitePk"/>
+          </div>
+          
+          <div class="view-content flex1">
+            <div class="section-title flexcr">
+              Site Summary
+              <div class="section-sub-title">(Currency Used: {{currencyData[siteForm.address.countryCode]}})</div>
+            </div>
+            <Summary
+              :info="summaryInfo"
+              :currency="currencyData[siteForm.address.countryCode]"/>
+          </div>
+          
+          <div class="view-content" id="idSiteTypes">
+            <div class="section-title">Site EMAIL Configuration</div>
+            <email-recipient v-model="siteForm.emails"/>
+          </div>
+        </div>
+      </div>
+      
+      <div class="view-content">
+        <div class="section-title">TIME CONFIGURATION</div>
+        <div style="overflow-x: auto;">
+          <site-type-with-time
+            :siteTypes="siteForm.siteTypes"
+            @change="changeSiteTypes"/>
+        </div>
+      </div>
+      
+      <div class="view-content" id="idChargeRates">
+        <div class="section-title flexcr">
+          CHARGE SITE RATE
+          <div class="section-sub-title">(Currency Used: {{currencyData[siteForm.address.countryCode]}})</div>
+        </div>
+        <charge-rate
+          v-model="ratesForm.chargeRates"/>
+        <div class="sparator"></div>
+        <div class="section-title flexcr">
+          SPECIAL CHARGE RATE
+          <div class="section-sub-title">(Currency Used: {{currencyData[siteForm.address.countryCode]}})</div>
+        </div>
+        <charge-rate
+          isSpecial
+          v-model="ratesForm.specialChargeRates"/>
+        <div class="sparator"></div>
+        <div class="section-title">Reservations</div>
+        <reservation
+          :enabled.sync="siteForm.enableReservation"
+          :limit.sync="siteForm.timeLimit"
+        />
+        <div class="sparator"></div>
+        <div class="section-title">LOAD BALANCING</div>
+        <Balancing v-model="balancingForm"/>
+        <template v-if="enableWhitelistFleet">
+          <div class="sparator"></div>
+          <div class="section-title">WHITELIST FLEET</div>
+          <white-list-fleet v-model="siteForm.fleetWhitelist"/>
+        </template>
+        
+        <template v-if="enableWhitelistUser">
+          <div class="sparator"></div>
+          <div class="section-title">WHITELIST USERS</div>
+          <white-list-user v-model="siteForm.whitelistUser"/>
+        </template>
+        <div class="sparator"></div>
+        <div class="section-title flexcwr">
+          Idle Fee SETTINGS
+          <div class="idle-switch">
+            <el-switch
+              inactive-text="Enable Idle Fee"
+              v-model="siteForm.enableIdleFee"/>
+          </div>
+        </div>
+        <el-row :gutter="20" v-if="siteForm.enableIdleFee">
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Effective From:"
+              label-width="130px"
+              prop="effectiveFrom">
+              <el-time-picker
+                v-model="siteForm.effectiveFrom"
+                class="input-text"
+                format="HH:mm"
+                value-format="HH:mm"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Effective To:"
+              label-width="130px"
+              prop="effectiveTo">
+              <el-time-picker
+                v-model="siteForm.effectiveTo"
+                class="input-text"
+                format="HH:mm"
+                value-format="HH:mm"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Idle Fee:"
+              label-width="130px"
+              prop="idleFee">
+              <el-input
+                v-model="siteForm.idleFee"
+                class="input-text"
+                oninput="value=value.replace(/[^\d.]/g, '').replace(/\.{2,}/g, '.').replace('.', '$#$').replace(/\./g, '').replace('$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').replace(/^\./g, '')"
+                maxlength="6"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Every (minute):"
+              label-width="130px"
+              prop="everyMinute">
+              <el-input
+                maxlength="4"
+                v-model.number="siteForm.everyMinute"
+                class="input-text"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Effective Date:"
+              label-width="130px"
+              prop="effectiveDate">
+              <el-date-picker
+                v-model="siteForm.effectiveDate"
+                class="input-text"
+                format="yyyy-MM-dd"
+                value-format="yyyy-MM-dd"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+      
+      <div class="view-content flexcr">
+        <div class="buttons">
+          <el-button
+            class="cancel-button"
+            type="primary"
+            @click="handleClickCancleButton">
+              Cancel
+          </el-button>
+          <el-button
+            class="confirm-button"
+            type="primary"
+            @click="handleClickSaveButton">
+              Save
+          </el-button>
+        </div>
+        <div
+          class="update-by"
+          v-if="isEdit">
+          <span
+            class="add-text"
+            :title='"CREATED BY " + siteForm.createdBy + " ON " + siteForm.createdOn'>
+            LAST UPDATED BY {{siteForm.updatedBy}} TIMESTAMP: {{siteForm.updatedOn}}
+          </span>
+        </div>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import Summary from './components/Summary'
+import Dashboard from './components/Dashboard'
+import ChargeRate from './components/ChargeRate'
+import Balancing from './components/Balancing'
+import EmailRecipient from './components/EmailRecipient'
+import Reservation from './components/Reservations'
+import WhiteListFleet from './components/WhiteListFleet'
+import WhiteListUser from './components/WhiteListUser'
+import SiteTypeWithTime from './components/SiteTypeWithTime'
+import waves from '@/directive/waves' // waves directive
+import site from '../../http/api/site'
+import { getRoleName } from '@/utils/auth'
+import { fetchFleetCompanyOptions } from '@/http/api/driver'
+export default {
+  directives: { waves },
+  components: {
+    Balancing,
+    ChargeRate,
+    Dashboard,
+    EmailRecipient,
+    Reservation,
+    WhiteListUser,
+    WhiteListFleet,
+    SiteTypeWithTime,
+    Summary
+  },
+  data() {
+    return {
+      pageLoading: false,
+      isMCSTUser: false,
+      countryOptions: [],
+      siteForm: {
+        sitePk: "",
+        siteName: "",
+        siteTypes: [],
+        emails: [],
+        carParkCode: "",
+        address: {
+          city: "",
+          countryCode: "SG",
+          street: "",
+          zipCode: ""
+        },
+        free: false,
+        endlessService: false,//24/7
+        additionalNotes: "",
+        //罚款部分
+        enableIdleFee: false,//idle Fee
+        idleFee: "",//idle Fee
+        everyMinute: "",//idle Fee
+        effectiveDate: "",//idle Fee
+        effectiveFrom: "",//idle Fee
+        effectiveTo: "",//idle Fee
+        //预订部分
+        enableReservation: false,//预订
+        timeLimit: null,//预订
+        
+        parkingFee: "",
+        whitelistUser: [],
+        fleetWhitelist: [],
+        operatingHours: "",
+        locationLatitude: "",
+        locationLongitude: "",
+        chargeRates: [],
+        specialChargeRates: [],
+        loadBalancing: "",//负载均衡
+        staticMaxAmpere: "",//负载均衡
+        siteChargingProfiles: null,//负载均衡
+      },
+      ratesForm: {
+        chargeRates: [{
+          rate: '',
+          rateType: '',
+          chargeTypePk: ''
+        }],
+        specialChargeRates: [{
+          rate: '',
+          rateType: '',
+          chargeTypePk: '',
+          fleetCompanyId: ''
+        }]
+      },
+      balancingForm: {
+        loadBalancing: 'dynamic',
+        staticMaxAmpere: 0,
+        siteChargingProfiles: [{
+          boxInUse: '',
+          chargingProfilePk: ''
+        }],
+      },
+      currencyData: {
+        SG: "S$"
+      },
+      autosize: {
+        minRows: 3,
+        maxRows: 6,
+      },
+      summaryInfo: {},
+      rules: {
+        siteName: {
+          required: true,
+          trigger: 'blur',
+          message: 'Site Name is required',
+        },
+        locationLatitude: [{
+          required: true,
+          trigger: 'blur',
+          message: 'Latitude is required',
+        }, {
+          pattern: /^[\-\+]?((0|([1-8]\d?))(\.\d{1,8})?|90(\.0{1,8})?)$/,
+          trigger: 'blur',
+          message: 'Please type a correct latitude',
+        }],
+        locationLongitude: [{
+          required: true,
+          trigger: 'blur',
+          message: 'Longitude is required',
+        }, {
+          pattern: /^[\-\+]?(0(\.\d{1,8})?|([1-9](\d)?)(\.\d{1,8})?|1[0-7]\d{1}(\.\d{1,8})?|180\.0{1,8})$/,
+          trigger: 'blur',
+          message: 'Please type a correct longitude',
+        }],
+        address: {
+          street: {
+            required: true,
+            trigger: 'blur',
+            message: 'Address is required',
+          },
+          zipCode: {
+            required: true,
+            trigger: 'blur',
+            message: 'Postal Code is required',
+          },
+          city: {
+            required: true,
+            trigger: 'blur',
+            message: 'City is required',
+          },
+          countryCode: {
+            required: true,
+            trigger: 'blur',
+            message: 'Country is required',
+          }
+        },
+        idleFee: {
+          required: true,
+          trigger: 'blur',
+          message: 'Idle fee is required'
+        },
+        effectiveDate: {
+          required: true,
+          trigger: 'change',
+          message: 'Effective Date is required'
+        },
+        effectiveFrom: {
+          required: true,
+          trigger: 'change',
+          message: 'Effective From is required'
+        },
+        effectiveTo: {
+          required: true,
+          trigger: 'change',
+          message: 'Effective To is required'
+        },
+        everyMinute: [{
+          required: true,
+          trigger: 'blur',
+          message: 'Every is required'
+        }, {
+          type: 'number',
+          message: 'Every is a number'
+        }]
+      },
+      enablePublic: false,
+      enableWhitelistUser: false,
+      enableWhitelistFleet: false,
+      isEdit: false
+    }
+  },
+  created() {
+    this.pageLoading = true;
+    this.isMCSTUser = getRoleName() === "MCST";
+    this.getCountryOptions()
+    if (this.$route.params.id) {
+      this.isEdit = true;
+      this.getSiteInfo()
+    } else {
+      setTimeout(() => {
+        this.pageLoading = false;
+      }, 500);
+    }
+  },
+  methods: {
+    getCountryOptions() {
+      site.getCountryList().then(res => {
+        if (res.data) {
+          this.countryOptions = res.data
+          const sign = {}
+          res.data.forEach(item => {
+            sign[item.value] = item.currencySymbol
+          })
+          this.currencyData = sign;
+        }
+      })
+    },
+    getSiteInfo() {
+      site.getSiteInfo({
+        sitePk: this.$route.params.id,
+      }).then(res => {
+        if (res.data) {
+          this.siteForm = res.data;
+          this.applySiteInfo();
+        }
+      }).finally(() => {
+        this.pageLoading = false;
+      })
+    },
+    applySiteInfo() {
+      if (this.siteForm.chargeRates) {
+        const rate = [], srate = []
+        this.siteForm.chargeRates.forEach(item => {
+          if (item.fleetCompanyId) {
+            srate.push(item)
+          } else {
+            rate.push(item)
+          }
+        })
+        if (rate.length > 0) {
+          this.ratesForm.chargeRates = rate;
+        }
+        if (srate.length > 0) {
+          this.ratesForm.specialChargeRates = srate;
+        }
+      }
+      if (this.siteForm.siteSummary) {
+        this.summaryInfo = Object.assign(this.siteForm.siteSummary, {});
+        delete this.siteForm.siteSummary;
+      }
+      this.balancingForm = {
+        loadBalancing: this.siteForm.loadBalancing || 'dynamic',
+        staticMaxAmpere: this.siteForm.staticMaxAmpere || 0,
+        siteChargingProfiles: this.siteForm.siteChargingProfiles || [{
+          boxInUse: '',
+          chargingProfilePk: ''
+        }]
+      }
+    },
+    changeEndless(value) {
+      if (value) {
+        this.siteForm.operatingHours = ""
+      }
+    },
+    changeParkingFree(value) {
+      if (value) {
+        this.siteForm.parkingFee = ""
+      }
+    },
+    changeSiteTypes(data) {
+      if (this.isEdit && this.pageLoading) {
+        console.log("changeSiteTypes", data);
+        return;
+      }
+      this.siteForm.siteTypes = data;
+      for (let type of data) {
+        if (type.siteType == "PRI_USR_WHI") {
+          this.enableWhitelistUser = (type.allDay || (type.startTime && type.endTime))
+        } else if (type.siteType == "PRI_FLE_WHI") {
+          this.enableWhitelistFleet = (type.allDay || (type.startTime && type.endTime))
+        } else if (type.siteType == "PUB") {
+          this.enablePublic = (type.allDay || (type.startTime && type.endTime))
+        } else {
+          continue;
+        }
+      }
+    },
+    handleClickCancleButton() {
+      this.$router.push({
+        path: "/site-management/index"
+      });
+    },
+    handleClickSaveButton() {
+      this.$refs['addForm'].validate(result => {
+        if (result) {
+          const rates = [];
+          this.ratesForm.chargeRates.forEach(item => {
+            if (item.rate) {
+              rates.push(item);
+            }
+          })
+          this.ratesForm.specialChargeRates.forEach(item => {
+            if (item.rate) {
+              rates.push(item);
+            }
+          })
+          if (rates.length == 0) {
+            this.$message({
+              message: "Please add at least one rate",
+              type: 'error',
+              duration: 3000,
+            })
+            this.scrollToView("idChargeRates", true);
+            return;
+          }
+          if (!this.siteForm.enableReservation) {
+            this.siteForm.timeLimit = "";
+          }
+          const chargeProfiles = []
+          this.balancingForm.siteChargingProfiles.forEach(item => {
+            if (item.boxInUse && item.chargingProfilePk)
+              chargeProfiles.push(item)
+          })
+          this.siteForm.siteChargingProfiles = chargeProfiles
+          if (this.balancingForm.loadBalancing == 'dynamic') {
+            this.siteForm.staticMaxAmpere = ""
+          } else {
+            this.siteForm.staticMaxAmpere = this.balancingForm.staticMaxAmpere
+          }
+          this.siteForm.loadBalancing = this.balancingForm.loadBalancing
+          if (!this.enablePublic && !this.enableWhitelistFleet && !this.enableWhitelistUser) {
+            this.$message({
+              message: "Please set time configuration",
+              type: 'error',
+              duration: 3000,
+            })
+            this.scrollToView("idSiteTypes");
+            return;
+          }
+          if (this.enableWhitelistUser) {
+            let _list = []
+            this.siteForm.whitelistUser.forEach(item => {
+              if (item.mobileNumber && item.licenceNumber) {
+                _list.push(item);
+              }
+            })
+            if (_list.length == 0) {
+              this.$message({
+                message: "Please add at least one whitelist user",
+                type: 'error',
+                duration: 3000,
+              })
+              return;
+            } else {
+              this.siteForm.whitelistUser = _list;
+            }
+          } else {
+            this.siteForm.whitelistUser = [];
+          }
+          if (this.enableWhitelistFleet) {
+            let _list = []
+            this.siteForm.fleetWhitelist.forEach(item => {
+              if (item.mobileNumber && item.licenceNumber) {
+                _list.push(item);
+              }
+            })
+            if (_list.length == 0) {
+              this.$message({
+                message: "Please add at least one whitelist fleet",
+                type: 'error',
+                duration: 3000,
+              })
+              return;
+            } else {
+              this.siteForm.fleetWhitelist = _list;
+            }
+          } else {
+            this.siteForm.fleetWhitelist = []
+          }
+          this.pageLoading = true;
+          console.log("form", this.siteForm);
+          if (this.isEdit) {
+            this.updateSite();
+          } else {
+            this.addSite()
+          }
+          //this.editSiteWithForm();
+        } else {
+          this.scrollToView("idSiteInfo", true);
+        }
+      })
+    },
+    scrollToView(id, alignToTop) {
+      //console.log("scrollToView", !alignToTop);
+      window.requestAnimationFrame(() => {
+        document.getElementById(id).scrollIntoView(!alignToTop, {
+          block: "start",
+          behavior: "smooth"
+        })
+      });
+    },
+    isSiteTypeEnable(index, isSwitch) {
+      for (let i = 0; i < this.siteForm.siteTypes.length; i++) {
+        let type = this.siteForm.siteTypes[i];
+        if (type.allDay) {
+          if (index == i && isSwitch) {
+            return false;
+          } else {
+            return true;
+          }
+        } else {
+          continue;
+        }
+      }
+      return false;
+    },
+    addSite() {
+      site.addSite(this.siteForm).then(res => {
+        this.$message({
+          message: "Add site success",
+          type: 'success',
+          duration: 3000,
+        })
+        this.handleClickCancleButton();
+      }).catch((error) => {
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      }).finally(() => {
+        this.pageLoading = false;
+      })
+    },
+    updateSite() {
+      site.updateSite(this.siteForm).then(res => {
+        this.$message({
+          message: "Update site success",
+          type: 'success',
+          duration: 3000,
+        })
+        this.handleClickCancleButton();
+      }).catch((error) => {
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      }).finally(() => {
+        this.pageLoading = false;
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang='scss'>
+  @import '../../styles/element-ui.scss';
+  @import '../../styles/variables.scss';
+  
+  .view-container {
+    width: 100%;
+    padding: 20px 60px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+  }
+  
+  .view-content {
+    margin: 0 8px 16px;
+    padding: 15px 50px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  
+  @media screen and (max-width: 800px) {
+    .view-container {
+      padding: 10px 20px;
+    }
+    .view-content {
+      padding: 15px 40px;
+    }
+  }
+  
+  @media screen and (max-width: 500px) {
+    .view-container {
+      padding: 0px;
+    }
+    .view-content {
+      padding: 15px 30px;
+    }
+  }
+  
+  .sparator {
+    margin: 10px -30px;
+    height: 2px;
+    background-color: #F0F5FC;
+  }
+  
+  .section-title {
+    color: #333333;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 15px;
+    line-height: 24px;
+    font-weight: 700;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  
+  .section-sub-title {
+    font-size: 14px;
+    padding-left: 5px;
+    font-weight: normal;
+  }
+  
+  .input-text {
+    width: 100% !important;
+    max-width: 300px;
+  }
+  
+  .add-text {
+    width: 100%;
+    font-size: 14px;
+    max-width: 300px;
+  }
+  
+  .add-text ::v-deep .el-textarea__inner,
+  .input-text ::v-deep .el-textarea__inner {
+    font-family: sans-serif;
+  }
+  
+  .cancel-button {
+    width: 94px;
+    height: 40px;
+  }
+  
+  .confirm-button {
+    width: 94px;
+    height: 40px;
+    margin-left: 20px;
+    background: $--color-primary;
+    border: 1px solid $--color-primary;
+    box-sizing: border-box;
+    border-radius: 4px;
+  }
+  
+  ::v-deep .el-switch__label.el-switch__label--left span {
+    font-size: 14px;
+    color: black;
+  }
+  .idle-switch {
+    font-size: 13px;
+    text-transform: none;
+  }
+</style>