/home/ivoiecob/email.hirewise-va.com/modules/AdminPanelWebclient/vue/src/components/EditUser.vue
<template>
  <q-scroll-area class="full-height full-width relative-position">
    <div class="q-pa-lg">
      <div class="row q-mb-md">
        <div class="col text-h5" v-if="!createMode" v-t="'COREWEBCLIENT.HEADING_COMMON_SETTINGS'"></div>
        <div class="col text-h5" v-if="createMode" v-t="'ADMINPANELWEBCLIENT.HEADING_CREATE_USER'"></div>
      </div>
      <q-card flat bordered class="card-edit-settings">
        <q-card-section>
          <component
            v-bind:is="mainDataComponent"
            ref="mainDataComponent"
            :currentTenantId="currentTenantId"
            :user="user"
            :createMode="createMode"
            @save="handleSave"
          />
          <div class="row q-mb-md">
            <div class="col-2" v-t="'ADMINPANELWEBCLIENT.LABEL_USER_NOTE'" />
            <div class="col-5 text-weight-medium">
              <q-input outlined dense bg-color="white" autogrow v-model="note" @keyup.enter.exact="save"/>
            </div>
          </div>
          <div class="row q-mb-md">
            <div class="col-2"></div>
            <div class="col-5">
              <q-checkbox dense v-model="isDisabled" :label="$t('ADMINPANELWEBCLIENT.ACTION_DEACTIVATE')" />
            </div>
          </div>
          <div class="row q-mb-md" v-if="userCreatedAtString">
            <div class="col-2" v-t="'ADMINPANELWEBCLIENT.LABEL_USER_CREATED'" />
            <div class="col-5 text-weight-medium">
              <span>{{ userCreatedAtString }}</span>
            </div>
          </div>
          <div class="row q-mb-md" v-if="!createMode">
            <div class="col-2" v-t="'ADMINPANELWEBCLIENT.LABEL_USER_LAST_LOGIN'" />
            <div class="col-5 text-weight-medium">
              <span v-if="userLastLoginAtString">{{ userLastLoginAtString }}</span>
              <span v-else v-t="'ADMINPANELWEBCLIENT.LABEL_USER_NEVER_LOGIN'"></span>
            </div>
          </div>
          <div class="row" v-if="allowMakeTenant">
            <div class="col-2"></div>
            <div class="col-5">
              <q-checkbox dense v-model="isTenantAdmin" :label="$t('ADMINPANELWEBCLIENT.LABEL_USER_IS_TENANT_ADMIN')" />
            </div>
          </div>
          <div class="row q-mt-md" v-if="!createMode && isUserSuperAdmin">
            <div class="col-2"></div>
            <div class="col-5">
              <q-checkbox
                dense
                v-model="writeSeparateLog"
                :label="$t('ADMINPANELWEBCLIENT.LABEL_LOGGING_SEPARATE_LOG_FOR_USER')"
              />
            </div>
          </div>
          <component
            v-for="component in otherDataComponents"
            :key="component.name"
            v-bind:is="component"
            ref="otherDataComponents"
            :currentTenantId="currentTenantId"
            :user="user"
            :createMode="createMode"
            @save="handleSave"
          />
          <div class="row q-mt-md" v-if="!createMode && allTenantGroups.length > 0 && isUserSuperAdmin">
            <div class="col-2 q-mt-sm" v-t="'ADMINPANELWEBCLIENT.LABEL_USER_GROUPS'"></div>
            <div class="col-10">
              <q-select
                dense
                outlined
                bg-color="white"
                use-input
                use-chips
                multiple
                v-model="selectedGroupOptions"
                :options="groupOptions"
                @filter="getGroupOptions"
              >
                <template v-slot:selected>
                  <span v-if="selectedGroupOptions" class="groups-container">
                    <q-chip
                      flat
                      v-for="option in selectedGroupOptions"
                      :key="option.value"
                      removable
                      @remove="removeFromSelectedGroups(option.value)"
                    >
                      <div class="ellipsis">
                        {{ option.label }}
                      </div>
                    </q-chip>
                  </span>
                </template>
                <template v-slot:no-option>
                  <q-item>
                    <q-item-section
                      class="text-grey"
                      v-t="'ADMINPANELWEBCLIENT.LABEL_GROUPS_NO_OPTIONS'"
                    ></q-item-section>
                  </q-item>
                </template>
                <template v-slot:option="scope">
                  <q-item v-close-popup v-bind="scope.itemProps" v-on="scope.itemEvents">
                    <q-item-section class="non-selectable">
                      <q-item-label>
                        {{ scope.opt.label }}
                      </q-item-label>
                    </q-item-section>
                  </q-item>
                </template>
              </q-select>
            </div>
          </div>
        </q-card-section>
      </q-card>
      <div class="q-pt-md text-right">
        <q-btn
          unelevated
          no-caps
          dense
          class="q-px-sm"
          :ripple="false"
          color="negative"
          @click="deleteUser"
          :label="$t('ADMINPANELWEBCLIENT.ACTION_DELETE_USER')"
          v-if="!createMode"
        >
        </q-btn>
        <q-btn
          unelevated
          no-caps
          dense
          class="q-px-sm q-ml-sm"
          :ripple="false"
          color="primary"
          @click="handleSave"
          :label="$t('COREWEBCLIENT.ACTION_SAVE')"
          v-if="!createMode"
        >
        </q-btn>
        <q-btn
          unelevated
          no-caps
          dense
          class="q-px-sm q-ml-sm"
          :ripple="false"
          color="primary"
          @click="handleSave"
          :label="$t('COREWEBCLIENT.ACTION_CREATE')"
          v-if="createMode"
        >
        </q-btn>
        <q-btn
          unelevated
          no-caps
          dense
          class="q-px-sm q-ml-sm"
          :ripple="false"
          color="secondary"
          @click="cancel"
          :label="$t('COREWEBCLIENT.ACTION_CANCEL')"
          v-if="createMode"
        >
        </q-btn>
      </div>
    </div>
    <q-inner-loading style="justify-content: flex-start" :showing="loading || deleting || saving">
      <q-linear-progress query />
    </q-inner-loading>
  </q-scroll-area>
</template>

<script>
import _ from 'lodash'

import errors from 'src/utils/errors'
import notification from 'src/utils/notification'
import typesUtils from 'src/utils/types'
import webApi from 'src/utils/web-api'

import cache from 'src/cache'
import modulesManager from 'src/modules-manager'
import settings from 'src/settings'

import moment from 'moment'

import UserModel from 'src/classes/user'

import enums from 'src/enums'
let UserRoles = {}

export default {
  name: 'EditUser',

  props: {
    deletingIds: Array,
    createMode: Boolean,
  },

  data() {
    return {
      mainDataComponent: null,
      otherDataComponents: [],

      user: null,
      publicId: '',
      isDisabled: false,
      isTenantAdmin: false,
      writeSeparateLog: false,
      note: '',

      selectedGroupOptions: [],
      groupOptions: [],

      loading: false,
      saving: false,
    }
  },

  computed: {
    createModeForEditUser() {
      const createIndex = this.$route.path.indexOf('/create')
      return createIndex !== -1 && createIndex === this.$route.path.length - 7
    },
    userCreatedAtString() {
      return this.user?.completeData?.CreatedAt ? moment(this.user?.completeData?.CreatedAt).format('L HH:mm') : null
    },
    userLastLoginAtString() {
      return this.user?.completeData?.LastLogin ? moment(this.user?.completeData?.LastLogin).format('L HH:mm') : null
    },
    currentTenantId() {
      return this.$store.getters['tenants/getCurrentTenantId']
    },

    allTenantGroups() {
      const groups = this.$store.getters['groups/getGroups']
      const allTenantGroups = typesUtils.pArray(groups[this.currentTenantId])
      return allTenantGroups.filter((group) => !group.isTeam)
    },

    deleting() {
      return this.deletingIds.indexOf(this.user?.id) !== -1
    },

    isUserSuperAdmin() {
      const isUserSuperAdmin = this.$store.getters['user/isUserSuperAdmin']
      return isUserSuperAdmin
    },

    allowMakeTenant() {
      const isUserSuperAdmin = this.$store.getters['user/isUserSuperAdmin']
      return isUserSuperAdmin && (settings.getEnableMultiTenant() || true)
    },
  },

  watch: {
    $route() {
      this.parseRoute()
    },

    'user.groups'() {
      if (_.isArray(this.user.groups)) {
        this.selectedGroupOptions = this.user.groups.map((group) => {
          return {
            label: group.name,
            value: group.id,
          }
        })
      }
    },
  },

  beforeRouteLeave(to, from, next) {
    this.$root.doBeforeRouteLeave(to, from, next)
  },

  beforeRouteUpdate(to, from, next) {
    this.$root.doBeforeRouteLeave(to, from, next)
  },

  mounted() {
    UserRoles = enums.getUserRoles()
    this.getUserMainDataComponent()
    this.getUserOtherDataComponents()
    this.loading = false
    this.saving = false
    this.parseRoute()
  },

  methods: {
    async getUserMainDataComponent() {
      this.mainDataComponent = await modulesManager.getUserMainDataComponent()
    },
    async getUserOtherDataComponents() {
      this.otherDataComponents = await modulesManager.getUserOtherDataComponents()
    },
    parseRoute() {
      if (this.$route.path === '/users' || this.$route.path === '/users/') {
        return
      }
      // TODO
      if (this.createMode || this.createModeForEditUser) {
        const user = new UserModel(this.currentTenantId, {})
        this.fillUp(user)
      } else {
        const userId = typesUtils.pPositiveInt(this.$route?.params?.id)
        if (this.user?.id !== userId) {
          this.user = {
            id: userId,
          }
          this.populate()
          this.getUserMainDataComponent()
        }
      }
    },

    clear() {
      this.publicId = ''
      this.isDisabled = false
      this.isTenantAdmin = false
      this.writeSeparateLog = false
      this.note = ''
    },

    fillUp(user) {
      this.user = user
      this.publicId = user.publicId
      this.isDisabled = user.disabled
      this.isTenantAdmin = user.role === UserRoles.TenantAdmin
      this.writeSeparateLog = user.writeSeparateLog
      this.note = user.note
      this.selectedGroupOptions = user.groups.map((group) => {
        return {
          label: group.name,
          value: group.id,
        }
      })
    },

    populate() {
      this.clear()
      this.loading = true
      cache.getUser(this.currentTenantId, this.user.id).then(({ user, userId }) => {
        if (userId === this.user.id) {
          this.loading = false
          if (user) {
            this.fillUp(user)
          } else {
            this.$emit('no-user-found')
          }
        }
      })
    },

    getGroupOptions(search, update, abort) {
      const searchLowerCase = search.toLowerCase()
      const selectedGroupsIds = this.selectedGroupOptions.map((option) => option.value)
      let groups = this.allTenantGroups.filter((group) => selectedGroupsIds.indexOf(group.id) === -1)
      if (searchLowerCase !== '') {
        groups = groups.filter((group) => group.name.toLowerCase().indexOf(searchLowerCase) !== -1)
      }
      update(() => {
        this.groupOptions = groups
          .map((group) => {
            return {
              label: group.name,
              value: group.id,
            }
          })
          .slice(0, 100)
      })
    },

    removeFromSelectedGroups(value) {
      this.selectedGroupOptions = this.selectedGroupOptions.filter((option) => option.value !== value)
    },

    /**
     * Method is used in doBeforeRouteLeave mixin
     */
    hasChanges() {
      if (this.loading) {
        return false
      }

      const hasMainDataChanges = _.isFunction(this.$refs?.mainDataComponent?.hasChanges)
        ? this.$refs.mainDataComponent.hasChanges()
        : false

      const hasOtherDataChanges = () => {
        if (Array.isArray(this.$refs?.otherDataComponents)) {
          return this.$refs.otherDataComponents.some((component) => {
            return typeof component.hasChanges === 'function' ? component.hasChanges() : false
          })
        }
        return false
      }

      return (
        hasMainDataChanges ||
        hasOtherDataChanges() ||
        this.isDisabled !== this.user?.disabled ||
        this.isTenantAdmin !== (this.user?.role === UserRoles.TenantAdmin) ||
        this.writeSeparateLog !== this.user?.writeSeparateLog ||
        this.note !== this.user?.note ||
        this.hasGroupChanges()
      )
    },

    hasGroupChanges() {
      const selectedIds = this.selectedGroupOptions.map((option) => option.value).sort()
      const userIds = (this.user.groups || []).map((group) => group.id).sort()
      return !_.isEqual(selectedIds, userIds)
    },

    /**
     * Method is used in doBeforeRouteLeave mixin,
     * do not use async methods - just simple and plain reverting of values
     * !! hasChanges method must return true after executing revertChanges method
     */
    revertChanges() {
      if (_.isFunction(this.$refs?.mainDataComponent?.revertChanges)) {
        this.$refs.mainDataComponent.revertChanges()
      }
      if (_.isFunction(this.$refs?.otherDataComponents?.forEach)) {
        this.$refs.otherDataComponents.forEach((component) => {
          if (_.isFunction(component.revertChanges)) {
            component.revertChanges()
          }
        })
      }
      this.isDisabled = this.user?.disabled
      this.isTenantAdmin = this.user?.role === UserRoles.TenantAdmin
      this.writeSeparateLog = this.user?.writeSeparateLog
      this.note = this.user?.note
    },

    isDataValid() {
      const isMainDataValid = _.isFunction(this.$refs?.mainDataComponent?.isDataValid)
        ? this.$refs.mainDataComponent.isDataValid()
        : true

      const isOtherDataValid = () => {
        if (Array.isArray(this.$refs?.otherDataComponents)) {
          return this.$refs.otherDataComponents.every((component) => {
            return _.isFunction(component.isDataValid) ? component.isDataValid() : true
          })
        }
        return true
      }

      return isMainDataValid && isOtherDataValid()
    },

    isUserEmailValid() {
      const userData = this.$refs.mainDataComponent.getSaveParameters()
      const userEmail = userData.PublicId
      const userNamePart = userEmail.slice(0, userEmail.lastIndexOf('@'))
      const invalidCharactersRegex = /[@\s]/
      return !invalidCharactersRegex.test(userNamePart) && userNamePart.length
    },

    handleSave() {
      this.isUserEmailValid()
        ? this.save()
        : notification.showError(this.$t('ADMINPANELWEBCLIENT.ERROR_INVALID_EMAIL_USERNAME_PART'))
    },

    save() {
      if (!this.saving && this.isDataValid()) {
        this.saving = true
        const mainDataParameters = _.isFunction(this.$refs?.mainDataComponent?.getSaveParameters)
          ? this.$refs.mainDataComponent.getSaveParameters()
          : {}
        const isUserSuperAdmin = this.$store.getters['user/isUserSuperAdmin']
        let parameters = _.extend(
          {
            UserId: this.user.id,
            TenantId: this.user.tenantId,
            Role: isUserSuperAdmin
              ? this.isTenantAdmin
                ? UserRoles.TenantAdmin
                : UserRoles.NormalUser
              : UserRoles.NormalUser,
            IsDisabled: this.isDisabled,
            WriteSeparateLog: this.writeSeparateLog,
            Note: this.note,
            Forced: true,
            GroupIds: isUserSuperAdmin ? this.selectedGroupOptions.map((option) => option.value) : null,
          },
          mainDataParameters
        )
        if (_.isFunction(this.$refs?.otherDataComponents?.forEach)) {
          this.$refs.otherDataComponents.forEach((component) => {
            const otherParameters = _.isFunction(component.getSaveParameters) ? component.getSaveParameters() : {}
            parameters = _.extend(parameters, otherParameters)
          })
        }

        webApi
          .sendRequest({
            moduleName: 'Core',
            methodName: this.createMode ? 'CreateUser' : 'UpdateUser',
            parameters,
          })
          .then(
            (result) => {
              this.saving = false
              if (this.createMode) {
                this.handleCreateResult(result, parameters)
              } else {
                this.handleUpdateResult(result, parameters)
              }
            },
            (response) => {
              this.saving = false
              const errorConst = this.createMode ? 'ERROR_CREATE_ENTITY_USER' : 'ERROR_UPDATE_ENTITY_USER'
              notification.showError(errors.getTextFromResponse(response, this.$t('ADMINPANELWEBCLIENT.' + errorConst)))
            }
          )
      }
    },

    handleCreateResult(result, parameters) {
      if (_.isSafeInteger(result)) {
        notification.showReport(this.$t('ADMINPANELWEBCLIENT.REPORT_CREATE_ENTITY_USER'))
        this.user.update(parameters)
        this.$emit('user-created', result)
      } else {
        notification.showError(this.$t('ADMINPANELWEBCLIENT.ERROR_CREATE_ENTITY_USER'))
      }
    },

    handleUpdateResult(result, parameters) {
      if (result === true) {
        cache.getUser(parameters.TenantId, parameters.UserId).then(({ user }) => {
          user.update(parameters, this.allTenantGroups)
          this.populate()
          this.$emit('user-updated', user.Id)
        })
        notification.showReport(this.$t('ADMINPANELWEBCLIENT.REPORT_UPDATE_ENTITY_USER'))
      } else {
        notification.showError(this.$t('ADMINPANELWEBCLIENT.ERROR_UPDATE_ENTITY_USER'))
      }
    },

    cancel() {
      this.revertChanges()
      this.$emit('cancel-create')
    },

    deleteUser() {
      this.$emit('delete-user', this.user.id)
    },
  },
}
</script>

<style scoped>
.groups-container {
  display: block;
  width: 100%;
}
</style>