![]() 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/ |
<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"> </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"> </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"> </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"> </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>