Spamworldpro Mini Shell
Spamworldpro


Server : Apache
System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64
User : corals ( 1002)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/corals/ts.corals.io/frontend/pages/reports/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/ts.corals.io/frontend/pages/reports/index.vue
<template>
  <div>
    <div v-is-admin>
      <div v-if="form.initialized">
        <div class="row">
          <div class="col-md-4">
            <corals-select :form="form" :multiple="true" field="client_id" label="Clients"
                           :options="form.getFormData('clients')"/>
          </div>
          <div class="col-md-4">
            <corals-select :form="form" label="Projects" field="project_id"
                           :options="this.projects"
                           :multiple="true"
                           @input="loadProjectRelatedFormData()"/>
          </div>
          <div class="col-md-4 fg-no-m">
            <corals-select :form="form" field="user_id" label="Users"
                           :multiple="true"
                           :options="this.users"/>
            <nuxt-link :to="`/users/${form.user_id}?${getFormattedUrlParams}`" class="text-sm d-block"
                       v-if="form.user_id && form.user_id.length === 1">
              Go to user profile
            </nuxt-link>
          </div>
        </div>
        <div class="row mb-3">
          <div class="col-md-2 fg-no-m">
            <corals-select :form="form" field="filter_id" label="Saved Filters"
                           :options="this.filters"/>
            <a href="#" class="text-danger text-sm d-block" v-if="this.showDeleteFilterButton"
               @click.prevent="deleteFilter(form.filter_id)">
              Delete
            </a>
          </div>
          <div class="col-md-2 d-flex">
            <corals-input type="text" :form="form"
                          label="New Filter Name"
                          field="filter_name"/>
            <div>
              <label class="d-none d-md-block">&nbsp;</label>
              <button class="btn btn-primary"
                      @click.prevent="saveFilter">
                Save
              </button>
            </div>
          </div>
          <div class="col-md-3 d-flex">
            <div>
              <label class="d-block">&nbsp;</label>
              <button :disabled="showGenerateButtonSpinner" class="btn btn-success" @click.prevent="submit(false)">
                <b-spinner v-if="showGenerateButtonSpinner" small></b-spinner>
                Generate Report
              </button>
            </div>
          </div>
        </div>
      </div>
      <div v-else>
        <div class="row">
          <div class="col-md-4">
            <div class="form-group">
              <label>Clients</label>
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
          <div class="col-md-4">
            <div class="form-group">
              <label>Projects</label>
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
          <div class="col-md-4">
            <div class="form-group">
              <label>User</label>
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-2">
            <b-skeleton type="input" animation="fade"></b-skeleton>
          </div>

          <div class="col-md-2">
            <b-skeleton type="input" animation="fade"></b-skeleton>
          </div>

          <div class="col-md-2">
            <b-skeleton type="button" animation="fade"></b-skeleton>
          </div>
        </div>
      </div>
    </div>
    <b-tabs content-class="mt-3 ">
      <b-tab v-if="form.initialized" title="Timesheet" active>
        <CoralsReportsDateForm @filter="submit(false)" :form="form">
          <template v-slot:custom-actions>
            <div class="col-md-3 d-flex justify-content-start">
              <!--              <div class="mr-2">-->
              <!--                <label class="d-block">&nbsp;</label>-->
              <!--                <button :disabled="showGenerateButtonSpinner" class="btn btn-success" @click.prevent="submit(false)">-->
              <!--                  <b-spinner v-if="showGenerateButtonSpinner" small></b-spinner>-->
              <!--                  Generate-->
              <!--                </button>-->
              <!--              </div>-->
              <div>
                <label class="d-block">&nbsp;</label>
                <button :disabled="showExcelButtonSpinner" class="btn btn-primary"
                        @click.prevent="submit(true)">
                  <b-spinner v-if="showExcelButtonSpinner" small></b-spinner>
                  <fa icon="file-excel"/>
                  Export
                </button>
              </div>
            </div>
          </template>
        </CoralsReportsDateForm>
        <div>
          <b-skeleton-table v-if="!reportData"
                            :rows="20"
                            :columns="9"
                            :table-props="{ bordered: true, striped: true }">
          </b-skeleton-table>
          <div class="table-responsive" v-else>
            <table class="table table-striped table-condensed">
              <tr v-for="(dayRecord,dayIndex) in reportData" :key="'day_'+dayIndex"
                  :class="getRowClass(dayRecord,dayIndex)">
                <template v-for="(colValue, columnIndex) in dayRecord">
                  <template v-if="columnIndex === 'details' && !['header','footer'].includes(dayIndex)">
                    <td>
                      <a v-if="dayRecord.details" @click="showDetailsModal(dayRecord)">
                        <fa icon="sticky-note"
                            class="text-primary cursor-pointer"/>
                      </a>
                      <span v-else> - </span>
                    </td>
                  </template>
                  <template v-else-if="columnIndex==='date' && !['header', 'footer'].includes(dayIndex)">
                    <td>
                      <nuxt-link :to="dayRecordLink(dayRecord,dayIndex)" target="_blank">
                        {{ colValue }}
                      </nuxt-link>
                    </td>
                  </template>
                  <template v-else>
                    <td v-if="columnIndex !== 'is_weekend'">
                      {{ colValue ? colValue : '-' }}
                    </td>
                  </template>
                </template>
              </tr>
            </table>
          </div>
        </div>
      </b-tab>
      <b-tab v-if="!form.initialized" title="Timesheet" active>
        <div class="row sm-gutters">
          <div class="col-md-3">
            <div class="form-group">
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
          <div class="col-md-3">
            <div class="form-group">
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
          <div class="col-md-3">
            <div class="form-group">
              <b-skeleton type="input" animation="fade"></b-skeleton>
            </div>
          </div>
          <div class="col-md-3 d-flex justify-content-start">
            <div>
              <div class="mr-2">
                <b-skeleton type="button" animation="fade"></b-skeleton>
              </div>
            </div>
            <div>
              <b-skeleton type="button" animation="fade"></b-skeleton>
            </div>
          </div>
        </div>
        <div>
          <b-skeleton-table
            :rows="20"
            :columns="9"
            :table-props="{ bordered: true, striped: true }">
          </b-skeleton-table>
        </div>
      </b-tab>
      <b-tab title="Limited Activities Report" v-if="form.user_id && form.user_id.length === 1">
        <LimitedActivities :user_id="this.form.user_id"/>
      </b-tab>
    </b-tabs>
    <b-modal :title="detailsModal.title" size="lg" ref="details-modal" @hidden="resetDetailsModal">
      <div v-if="detailsModal.activeDayRecord">
        <ul class="list-unstyled">
          <li v-for="(detailsItem,index) in detailsModal.activeDayRecord.details">
            <h5>{{ detailsItem.project }} - {{ detailsItem.client }}</h5>
            <div class="row">
              <div class="col-md-2">
                <strong class="time-label text-center">
                  <fa icon="hourglass-start"/>
                  {{ detailsItem.time }}
                </strong>
              </div>
              <div class="col-md-10">
                <span v-html="$getTextWithLinks(detailsItem.description)"></span>
              </div>
            </div>
            <hr v-if="(index+1) !== detailsModal.activeDayRecord.details.length"/>
          </li>
        </ul>
      </div>
    </b-modal>
  </div>
</template>

<script>
import LimitedActivities from "@/components/LimitedActivities/LimitedActivities";
import BackToTop from 'vue-backtotop'
import CoralsReportsDateForm from "@/components/CoralsForm/CoralsReportsDateForm";
import commonMixin from "@/mixins/commonMixin";

import index from "@/components/Entries/Index.vue";
import CoralsInput from "@/components/CoralsForm/CoralsInput.vue";
import CoralsSelect from "@/components/CoralsForm/CoralsSelect.vue";

export default {
  name: "Reports",
  computed: {
    getFormattedUrlParams() {
      return this.objectAsQueryString(JSON.parse(JSON.stringify(this.$route.query)));
    },
    index() {
      return index
    },
    showDeleteFilterButton() {
      return !!this.form.filter_id;
    }
  },
  middleware: 'Authorization',
  mixins: [commonMixin],
  components: {CoralsSelect, CoralsInput, LimitedActivities, BackToTop, CoralsReportsDateForm},

  data() {
    return {
      showGenerateButtonSpinner: false,
      showExcelButtonSpinner: false,
      reportData: null,
      urlSearchParams: null,
      users: [],
      projects: [],
      filters: [],
      selectedFilter: null,
      detailsModal: {
        activeDayRecord: null,
        title: '',
      },
      form: this.$form({
        period: 'currentMonth',
        from_date: '',
        to_date: '',
        user_id: '',
        excel: 0,
        project_id: '',
        client_id: '',
        filter_id: '',
        filter_name: ''
      }, {
        fetchFormDataURL: 'timesheet/reports/get-form-data', resetOnSuccess: false,
        loadFormDataCallBack: () => {
          this.projects = this.getProjectsDataOptions(this.form.getFormData('projects'), false);
          this.users = this.form.getFormData('users');
          this.filters = this.form.getFormData('filters');
        }
      }),
    }
  },
  methods: {
    loadProjectRelatedFormData() {

      let url = `timesheet/reports/get-form-data?project_ids=${this.form.project_id}`;

      this.$axios.get(url,).then(({data: response}) => {
        this.users = [];
        this.users = response.data.users
      })
    },

    loadUsersPerClientFormData() {

      let url = `timesheet/reports/get-form-data?client_ids=${this.form.client_id}`;

      this.$axios.get(url).then(({data: response}) => {
        this.users = [];
        this.users = response.data.users
      })
    },

    getProjectsDataOptions(arrayData, filterByClient) {
      let resultsData = [];
      let DataGroups = [];
      let activeObjects = [];


      if (!filterByClient) {
        activeObjects = arrayData.filter((object => object['group'] !== true));
        DataGroups = arrayData.filter((group => group['group'] === true));
      } else {
        activeObjects = arrayData.filter((object => object['group'] !== true && this.form.client_id.includes(object['group'])));
        DataGroups = arrayData.filter((group => group['group'] === true && this.form.client_id.includes(group['code'])));
      }


      DataGroups.forEach(group => {
        group['is_group_empty'] = true;
        resultsData.push(group);
        activeObjects.filter(activeObject => {
          if (group['code'] === activeObject['group']) {
            group['is_group_empty'] = false;
            resultsData.push(activeObject);
          }
        });
      });

      return resultsData;
    },
    showDetailsModal(dayRecord) {
      this.detailsModal.activeDayRecord = dayRecord;
      this.detailsModal.title = dayRecord.date + ' Details';
      this.$refs['details-modal'].show()
    },
    resetDetailsModal() {
      this.detailsModal = {
        activeDayRecord: null,
        title: '',
      }
    },
    dayRecordLink(dayRecord, dayIndex) {
      let link = `/?date=${dayIndex}`;

      if (this.form.user_id && this.form.user_id.length === 1) {
        link += `&user_id=${this.form.user_id}`
      }

      return link;
    },
    submit(excelFlag = false) {
      this.form.excel = excelFlag;

      if (!excelFlag) {
        this.reportData = null;
        this.showGenerateButtonSpinner = true;
      } else {
        this.showExcelButtonSpinner = true;
      }

      this.form.post('timesheet/reports/get-report-data', excelFlag ? {responseType: 'blob'} : {}).then((response) => {
        if (!excelFlag) {
          this.reportData = response.data.data.report_data;
          this.showGenerateButtonSpinner = false;
          return;
        }

        this.showExcelButtonSpinner = false;

        let disposition = response.headers['content-disposition'];

        let filename = 'download';

        if (disposition && disposition.indexOf('attachment') !== -1) {
          let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
          let matches = filenameRegex.exec(disposition);
          if (matches != null && matches[1]) {
            filename = matches[1].replace(/['"]/g, '');
          }
        }
        let blob = new Blob([response.data], {type: response.headers['content-type']});
        let url = window.URL.createObjectURL(blob);
        let link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', filename);
        link.click();
      });
    },
    getRowClass(dayRecord, dayIndex) {
      if (dayRecord.is_weekend === true) {
        return 'bg-light'
      }
      if (!['header', 'footer'].includes(dayIndex)) {
        return ''
      }

      let trClass = 'bg-dark text-light'

      if (dayIndex === 'header') {
        trClass += ' position-sticky';
      }

      return trClass;
    },
    saveFilter() {
      this.form.post(`timesheet/reports/save-filter`).then((response) => {
        this.loadFilterFormData(response.data.data.new_filter_id);
      }).catch(err => {
        this.$toast.error(err.message);

      });
    },
    deleteFilter(filter_id) {
      let url = `timesheet/reports/delete-filter?filter_id=${filter_id}`;
      this.$axios.delete(url).then(({data: response}) => {
        this.loadFilterFormData();
        this.submit()
        this.$toast.success(response.message);
      }).catch(err => {
        this.$toast.error(err.message);
      });
    },

    loadFilterFormData(filterId = '') {
      let url = `timesheet/reports/get-form-data`;
      this.$axios.get(url,).then(({data: response}) => {
        this.filters = [];
        this.filters = response.data.filters
        this.form.filter_id = filterId
        this.form.filter_name = ''
      })
    },
  },
  watch: {
    'form.client_id': function (val) {
      if (!this.form.project_id.length) {
        this.loadUsersPerClientFormData();
      }
      if (val.length) {
        this.projects = this.getProjectsDataOptions(this.form.getFormData('projects'), true);
      } else {
        this.projects = this.getProjectsDataOptions(this.form.getFormData('projects'), false);
      }
    },
    'form.project_id': function (val) {
      if (!val.length) {
        this.loadUsersPerClientFormData();
      } else {
        this.loadProjectRelatedFormData();
      }
    },
    'form.filter_id': function (val) {

      let filters = ['client_id', 'project_id', 'user_id'];

      filters.forEach(filter => {
        this.form[filter] = [];
      });

      if (val) {
        this.selectedFilter = this.filters.filter((object => val === object['value']));

        let selectedFilter = JSON.parse(this.selectedFilter[0].filter);

        filters.forEach(filter => {
          if (selectedFilter[filter]) {
            this.form[filter] = selectedFilter[filter].map(filter_id => parseInt(filter_id, 10));
          }
        });

        this.submit();
      }
    },
  }
}
</script>

<style>

.position-sticky {
  top: 0;
  z-index: 1;
}

@media screen and (min-width: 768px) {
  .table-responsive {
    overflow: visible;
  }
}


.position-sticky td {
  min-width: 100px;
}
</style>


Spamworldpro Mini