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/Invoices.vue
<template>
  <crud-index
    ref="invoicesTable"
    :columns="columns"
    :deleteHref="deleteHref"
    :editHref="editHref"
    :bulk-action-options="bulkActionOptions"
    :resourceURL="invoicesResource" :options="options" :labels="labels" :form="form" form-size="lg">

    <template v-slot:code="{object}">
      <nuxt-link :to="`/invoices/${object.row.id}`">
        {{ object.row.code }}
      </nuxt-link>
    </template>

    <template v-slot:status="{object}">
      <span v-html="object.row.status"></span>
    </template>
    <template v-slot:total="{object}">
      <table class="table totals-table table-borderless">
        <tr v-if="parseFloat(object.row.total) !== parseFloat(object.row.subtotal)">
          <td>Subtotal</td>
          <td class="text-right">{{ object.row.subtotal }}</td>
        </tr>
        <tr v-if="parseFloat(object.row.tax_total)">
          <td>Taxes</td>
          <td class="text-right">{{ object.row.tax_total }}</td>
        </tr>
        <tr v-if="parseFloat(object.row.discount_total)">
          <td>Discount</td>
          <td class="text-right">{{ object.row.discount_total }}</td>
        </tr>
        <tr>
          <td><b>Total</b></td>
          <td class="text-right"><b>{{ object.row.total }}</b></td>
        </tr>
      </table>
    </template>

    <template v-slot:notes="{object}">
      <fa v-if="object.row.notes" icon="sticky-note"
          v-b-popover.click.blur="object.row.notes"
          class="text-primary cursor-pointer"/>
      <span v-else>{{ object.row.notes }}</span>
    </template>

    <template v-slot:default="{form}">
      <div>
        <div class="row sm-gutters">
          <div class="col-md-6">
            <corals-radio :form="form" field="status" label="Status" :list="form.getFormData('status_options')"
                          required/>
          </div>
          <div class="col-md-6">
            <corals-select :form="form" label="Project" field="project_id"
                           :options="form.getFormData('client_projects_options')"/>
          </div>
        </div>
        <div class="row sm-gutters">
          <div class="col-md-6">
            <corals-datepicker :form="form" field="invoice_date" label="Invoice Date"/>
          </div>
          <div class="col-md-6">
            <corals-datepicker :form="form" field="due_date" label="Due Date"/>
          </div>
        </div>

        <corals-textarea :form="form" field="notes" placeholder="Place invoice notes here..."/>
      </div>
      <hr/>
      <div id="invoice-items">
        <div :key="'item_'+index" class="p-1 position-relative" v-for="(item,index) in form.items"
             :style="(index % 2 !== 0) ? {'background-color': 'rgba(0, 0, 0, 0.05)'} :''">
          <div class="row sm-gutters">
            <div class="col-md-8">
              <div class="row sm-gutters">
                <div class="col-md-7">
                  <corals-input :form="form" size="sm" type="text" :field="`items.${index}.title`" v-model="item.title"
                                label="Title"
                                required/>
                </div>
                <div class="col-md-5">
                  <label class="required-field">Type</label>
                  <b-form-select
                    size="sm"
                    v-model="item.type"
                    :options="form.getFormData('type_options')"
                    text-field="label"
                    value-field="id"
                    required
                  ></b-form-select>
                </div>
              </div>
              <div class="row sm-gutters">
                <div class="col-md-4">
                  <corals-input :form="form" size="sm" type="number" :field="`items.${index}.quantity`"
                                v-model="item.quantity"
                                label="Quantity"
                                step="0.01" required/>
                </div>
                <div class="col-md-4">
                  <corals-input :form="form" size="sm" type="number" :field="`items.${index}.rate`" v-model="item.rate"
                                label="Rate"
                                step="0.01" required/>
                </div>
                <div class="col-md-4">
                  <label class="d-block">Amount</label>
                  <div class="">
                    <b-form-input size="sm" :value="Math.round(item.quantity * item.rate * 100) / 100"
                                  readonly></b-form-input>
                  </div>
                </div>
              </div>
            </div>
            <div class="col-md-4">
              <corals-textarea :form="form" size="sm" :field="`items.${index}.notes`" v-model="item.notes" label="Notes"
                               rows="4"
                               placeholder="Item Notes..."/>
            </div>
          </div>
          <a href="#" @click.prevent="removeItem(index)" class="text-danger clear-item-btn">
            <fa icon="times"/>
          </a>
        </div>
        <button @click.prevent="addItem" class="btn btn-sm btn-success mt-1">
          <fa icon="plus"/>
          Add Item
        </button>
      </div>
    </template>

  </crud-index>


</template>

<script>
import CrudIndex from "@/components/layout/CRUDIndex";

export default {
  name: "ClientsInvoicesIndex",
  components: {CrudIndex},
  provide() {
    return {
      handleBulkAction: this.handleBulkAction,
    }
  },
  props: {
    client: {
      required: true
    }
  },
  data() {
    return {
      form: this.$form({
        notes: '',
        status: 'pending',
        project_id: '',
        invoice_date: new Date(),
        due_date: new Date(),
        items: [
          this.getItemObject()
        ],
      }, {
        fetchFormDataURL: `timesheet/invoices/get-form-data?client_id=${this.client.id}`,
        model: 'invoice',
        loadFormDataCallBack: () => {
          this.options.listColumns.status = this.getOptions(this.form.formData.status_options);
        }
      }),
      labels: {
        title: 'Invoices',
        singularTitle: 'Invoice',
      },
      options: {
        hideCreate: this.$notAdmin(),
        listColumns: {
          status: [],
        },
        customColumns: ['code', 'status', 'total', 'notes'],
        filterable: ['code', 'status', 'notes', 'invoice_date'],
        headings: {
          'total': 'Summary',
          'total_hours': 'Hours',
          'project_name': 'Project'
        }
      },
    }
  },
  methods: {
    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;
    },
    handleBulkAction(action, selectedIds) {
      this.$bvModal.msgBoxConfirm('Are you sure?', {
        size: 'md',
        buttonSize: 'sm',
        okVariant: 'danger',
        footerClass: 'p-2',
        hideHeaderClose: false,
        centered: true
      })
        .then(value => {
          if (value === true) {
            this.$axios.post('timesheet/invoices/bulk-action', {
              'selection': selectedIds,
              'action': action
            }).then(response => {
              let level = response.data.data.level;
              let message = response.data.data.message;

              if (level === 'success') {
                this.$toast.success(message);
              } else {
                this.$toast.error(message);
              }

              this.$eventBus.$emit('setSelectedIds', []);

              this.$refs.invoicesTable.refresh();
            }).catch(err => {
              this.$toast.error(err.response.data.message);
            });
          }
        })
        .catch(err => {
        })
    },
    getItemObject() {
      return {
        'title': '',
        'type': 'item',
        'rate': '',
        'quantity': 1,
        'notes': ''
      }
    },
    addItem() {
      this.form.items.push(this.getItemObject());
    },
    removeItem(itemIndex) {
      let result = [];
      this.form.items.forEach((arr, index) => {
        if (index != itemIndex) {
          result.push(arr);
        }
      });
      this.form.items = result;
    }
  },
  computed: {
    deleteHref() {
      return `timesheet/invoices`;
    },
    editHref() {
      return `timesheet/invoices`
    },
    columns() {
      let columns = [];
      if (this.$isAdmin() && this.bulkActionOptions.length) {
        columns.push('checkbox');
      }
      columns.push('code', 'project_name', 'total', 'total_hours', 'invoice_date', 'notes', 'status', 'actions');

      if (this.$notAdmin()) {
        columns.pop();
      }
      return columns;
    },
    invoicesResource() {
      return `/timesheet/invoices/by-client/${this.client.id}`;
    },
    bulkActionOptions() {
      if (this.$notAdmin()) {
        return [];
      }

      return ['delete', 'pending', 'paid', 'void'];
    },
  },
}
</script>

<style scoped>
.totals-table {
  margin-bottom: 0;
}

.totals-table tr, .totals-table td {
  background-color: unset !important;
  padding: 2px;
}

.clear-item-btn {
  position: absolute;
  top: 0;
  right: 5px;
}

#invoice-items .form-group {
  margin-bottom: 0.5rem;
}

#invoice-items >>> label {
  margin-bottom: 0.25rem;
}
</style>

Spamworldpro Mini