![]() 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/components/Clients/ |
<template> <crud-index ref="projectsTable" :columns="columns" :resourceURL="projectsResource" :options="options" :form="form" :labels="labels" :form-size="'lg'"> <template v-slot:name="{object}"> <nuxt-link :to="`/clients/${object.row.client_id}/projects/${object.row.id}`"> {{ object.row.name }} </nuxt-link> </template> <template v-slot:status="{object}"> <span v-html="object.row.status_as_labels"></span> </template> <template v-slot:assignable="{object}"> <fa icon="check" class="text-success" v-if="object.row.assignable"/> <span v-else>-</span> </template> <template v-slot:taxable="{object}"> <fa icon="check" class="text-success" v-if="object.row.taxable"/> <span v-else>-</span> </template> <template v-slot:extra-actions="{object}"> <b-link class="text-danger" @click.prevent="generateInvoice(object)" v-b-tooltip.hover title="Generate Invoice" v-if="object.generateInvoice"> <fa icon="file-invoice"/> </b-link> </template> <template v-slot:default="{form}"> <b-tabs content-class="mt-3"> <b-tab title="Details" active> <div class="row"> <div class="col-md-6"> <corals-input :form="form" field="name" required/> <corals-input :form="form" label="Integration Key" field="integration_id"/> <corals-checkbox field="assignable" :form="form"/> <corals-checkbox field="taxable" :form="form"/> <corals-radio :form="form" field="status" label="Status" :list="form.getFormData('status_options')" required/> </div> <div class="col-md-6"> <corals-datepicker :form="form" field="start_date" label="Start Date"/> <div class="row sm-gutters"> <div class="col-md-6"> <corals-select :form="form" field="bill_cycle" :options="billCycleOptions" description="Will overrides the client settings"/> </div> <div class="col-md-6"> <corals-input type="number" :form="form" label="Start of Cycle" description="Day of month" v-if="form.bill_cycle === 'monthly' || form.bill_cycle === 'biweekly'" field="bill_cycle_starts_at" min="1"/> <corals-select :form="form" field="bill_cycle_starts_at" label="Start of Cycle" v-else-if="form.bill_cycle && form.bill_cycle === 'weekly'" :options="weekDaysOptions"/> </div> </div> <corals-textarea :form="form" field="description"/> </div> </div> </b-tab> <b-tab title="Labels"> <div class="row mb-2"> <div class="col-md-10"> <b-form-group label="Label" label-for="label_text_value" :state="dontShowLabelFieldError" :invalid-feedback="labelTextFieldErrorMessage"> <b-form-input :state="dontShowLabelFieldError" type="text" id="label_text_value" v-model.trim="labelTextField"> </b-form-input> </b-form-group> </div> <div class="col-md-1 " style="margin-top: 33px"> <a class="btn btn-success btn-sm" @click.prevent="pushLabelToProjectLabels"> <fa icon="plus"/> </a> </div> </div> <div class="row"> <div class="col-md-12"> <table class="table"> <thead> <tr> <th>Label</th> <th>Is active?</th> <th v-if="false">Action(s)</th> </tr> </thead> <tbody> <tr v-for="label of form.labels"> <td v-html="label.name"></td> <td> <b-form-checkbox v-if="label" v-model="label.status" value="active" unchecked-value="inactive"/> </td> <td v-if="false"> <a href="#" @click.prevent="removeLabel(label)"> <fa icon="trash" class="text-danger"/> </a> </td> </tr> </tbody> </table> </div> </div> </b-tab> <b-tab title="Users" v-if="form.assignable"> <p class="text-danger" v-if="form.error('user_rates')"> {{ form.error('user_rates') }} </p> <table class="table table-striped table-condensed"> <thead> <tr> <th>User</th> <th>Rate</th> </tr> </thead> <tbody> <tr v-for="user in form.getFormData('users')"> <td class="align-middle"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" @change="setProjectUserRateCheckbox(user.hourly_rate,user.id,$event.target.checked)" :id="`user_${user.id}`" :value="user.id" :checked="userChecked(user.id)"> <label class="custom-control-label" :for="`user_${user.id}`"> {{ user.label }} </label> </div> </td> <td> <input class="form-control w-75" :value="getUserHourlyRate(user.id,form.getFormData('users'))" :id="`user_rate_${user.id}`" type="number" @input="setProjectUserRate($event.target.value,user.id)"> </td> </tr> </tbody> </table> </b-tab> <b-tab title="Activities"> <p class="text-danger" v-if="form.error('activity_rates')"> {{ form.error('activity_rates') }} </p> <table class="table table-striped table-condensed"> <thead> <tr> <th>Title</th> <th>Rate</th> </tr> </thead> <tbody> <tr class="table-secondary text-white"> <td colspan="2" class="p-2"> <strong>Billable Activities</strong> </td> </tr> <tr v-for="activity in form.getFormData('billable_activities')"> <td class="align-middle"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" @change="setProjectActivityRateCheckbox(activity.hourly_rate,activity.id,$event.target.checked)" :id="`activity_${activity.id}`" :value="activity.id" :checked="activityChecked(activity.id)"> <label class="custom-control-label" :for="`activity_${activity.id}`"> {{ activity.title }} </label> </div> </td> <td class="w-50"> <input class="form-control w-50" :value="getActivityHourlyRate(activity.id,form.getFormData('billable_activities'))" :id="`activity_rate_${activity.id}`" type="number" @input="setProjectActivityRate($event.target.value,activity.id)"> </td> </tr> </tbody> <tbody> <tr class="table-secondary text-white"> <td colspan="2" class="p-2"> <strong>Non Billable Activities</strong> </td> </tr> <tr v-for="activity in form.getFormData('non_billable_activities')"> <td class="align-middle"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" @change="setProjectActivityRateCheckbox(activity.hourly_rate,activity.id,$event.target.checked)" :id="`activity_${activity.id}`" :value="activity.id" :checked="activityChecked(activity.id)"> <label class="custom-control-label" :for="`activity_${activity.id}`"> {{ activity.title }} </label> </div> </td> <td class="w-50"> <input class="form-control w-50" :value="getActivityHourlyRate(activity.id,form.getFormData('non_billable_activities'))" :id="`activity_rate_${activity.id}`" type="number" @input="setProjectActivityRate($event.target.value,activity.id)"> </td> </tr> </tbody> </table> </b-tab> <b-tab title="Project Type"> <b-tabs pills vertical content-class="min-h-400" v-model="currentTabIndex"> <project-types :form="form"/> </b-tabs> </b-tab> </b-tabs> </template> </crud-index> </template> <script> import CrudIndex from "@/components/layout/CRUDIndex"; import commonMixin from "@/mixins/commonMixin"; import CoralsCheckboxes from "../CoralsForm/CoralsCheckboxes"; import ProjectTypes from "@/components/ProjectTypes/ProjectTypes"; export default { name: "ClientsProjectIndex", components: {CoralsCheckboxes, CrudIndex, ProjectTypes}, mixins: [commonMixin], props: { client: { required: true } }, data() { return { project_types: ['time_and_materials', 'fixed_fee', 'non_billable'], currentTabIndex: 0, labelTextFieldErrorMessage: '', labelTextField: '', dontShowLabelFieldError: null, labels: { title: 'Projects', singularTitle: 'Project', }, columns: ['name', 'hourly_rate', 'budget', 'project_type_as_label', 'budget_by_as_label', 'assignable', 'taxable', 'status', 'actions'], options: { listColumns: { taxable: this.yesNoOptions(), assignable: this.yesNoOptions(), status: [], }, initFilters: {'status': 'active'}, sortable: ['name'], filterable: ['name', 'taxable', 'assignable', 'status'], customColumns: ['name', 'taxable', 'assignable', 'status'], hideCreate: this.$cant('create', 'project'), headings: { 'budget_by_as_label': 'Budget By', 'project_type_as_label': 'Type' } }, form: this.$form({ name: '', description: '', budget: '', hourly_rate: '', assignable: 0, taxable: 0, status: 'active', bill_cycle: '', bill_cycle_starts_at: '', currency: 'usd', start_date: '', fee: '', type: 'time_and_materials', budget_by: '', bill_by: '', send_email_on_exceed_limit: 0, reset_budget_each_month: 0, budget_alert_limit: '', user_rates: [], activity_rates: [], labels: [], integration_id: '' }, { fetchFormDataURL: '/timesheet/clientProjects/get-form-data', loadFormDataCallBack: () => { this.options.listColumns.status = this.getOptions(this.form.formData.status_options); } }), billCycleOptions: { daily: 'Daily', weekly: 'Weekly', biweekly: 'Biweekly', monthly: 'Monthly' }, weekDaysOptions: { 0: 'Sunday', 1: 'Monday', 2: 'Tuesday', 3: 'Wednesday', 4: 'Thursday', 5: 'Friday', 6: 'Saturday' }, } }, methods: { getActivityHourlyRate(activity_id, activities) { let projectActivity = this.form.activity_rates.find(activity => activity.activity_id === activity_id); let defaultActivityData = activities.find(activity => activity.id === activity_id); if (projectActivity) { return projectActivity.activity_rate; } else if (defaultActivityData.hourly_rate !== null) { return defaultActivityData.hourly_rate; } }, activityChecked(activity_id) { let projectActivity = this.form.activity_rates.find(activity => activity.activity_id === activity_id); return !!projectActivity; }, setProjectActivityRateCheckbox(rateValue, activity_id, isChecked) { if (isChecked) { this.form.activity_rates.push({ 'activity_id': activity_id, 'activity_rate': rateValue }); } else { //remove the activity id. this.form.activity_rates = this.form.activity_rates.filter(activityRate => activityRate.activity_id !== activity_id); } }, setProjectActivityRate(rateValue, activity_id) { let activityRate = this.form.activity_rates.find(activityRate => activityRate.activity_id === activity_id); if (!activityRate) { return; } if (activityRate) { activityRate.activity_rate = rateValue; } }, userChecked(user_id) { let projectUser = this.form.user_rates.find(user => user.user_id === user_id); return !!projectUser; }, getUserHourlyRate(user_id, users) { let projectUser = this.form.user_rates.find(user => user.user_id === user_id) let defaultUserData = users.find(user => user.id === user_id); if (projectUser) { return projectUser.user_rate; } else if (defaultUserData.hourly_rate !== null) { return defaultUserData.hourly_rate; } }, setProjectUserRateCheckbox(rateValue, user_id, isChecked) { if (isChecked) { this.form.user_rates.push({ 'user_id': user_id, 'user_rate': rateValue }); } else { //remove the user id. this.form.user_rates = this.form.user_rates.filter(userRate => userRate.user_id !== user_id); } }, setProjectUserRate(rateValue, user_id) { let userRate = this.form.user_rates.find(userRate => userRate.user_id === user_id); if (!userRate) { return; } if (userRate) { userRate.user_rate = rateValue; } }, getOptions(list) { let allOption = {id: '', text: 'All'}; let options = []; options.push(allOption); list.map(row => { let label = row.label.split(","); options.push({id: row.id ?? row.value, text: label[0]}); }); return options; }, removeLabel(label) { this.$swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#d7d7d7', confirmButtonText: 'Yes, delete it!' }).then((result) => { if (result.value) { this.form.labels = this.form.labels.filter(l => { return l.name != label.name; }) } }); }, pushLabelToProjectLabels() { this.dontShowLabelFieldError = true; if (!this.labelTextField || !this.labelTextField.length) { this.dontShowLabelFieldError = false this.labelTextFieldErrorMessage = "The label shouldn't be empty"; this.$toast.error(this.labelTextFieldErrorMessage); return; } this.form.labels.push({ name: this.labelTextField, status: 'active', id: null }); this.labelTextField = ''; this.dontShowLabelFieldError = null; this.labelTextFieldErrorMessage = ''; }, generateInvoice(project) { this.$axios.post(`${this.projectsResource}/${project.id}/generate-invoice`) .then(response => { this.$toast.success(response.data.message); this.$refs.projectsTable.refresh(); }).catch(err => { this.$toast.error(err.message); }) } }, watch: { 'form.type'() { if (this.form.type !== null) { this.currentTabIndex = this.project_types.findIndex(type => { return type === this.form.type; }); } }, 'currentTabIndex'() { if (this.currentTabIndex !== -1) { this.form.type = this.project_types[this.currentTabIndex] } return ''; } }, computed: { projectsResource() { return '/timesheet/clients/' + this.client.id + '/projects'; } } } </script> <style scoped> >>> .min-h-400 { min-height: 400px; } </style>