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/components/Clients/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/ts.corals.io/frontend/components/Clients/Projects.vue
<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>

Spamworldpro Mini