관리-도구
편집 파일: index_quotation.vue
<template> <div class="main-content"> <breadcumb :page="$t('ListQuotations')" :folder="$t('Quotations')"/> <div v-if="isLoading" class="loading_page spinner spinner-primary mr-3"></div> <div v-else> <vue-good-table mode="remote" :columns="columns" :totalRows="totalRows" :rows="quotations" @on-page-change="onPageChange" @on-per-page-change="onPerPageChange" @on-sort-change="onSortChange" @on-search="onSearch" :search-options="{ enabled: true, placeholder: $t('Search_this_table'), }" :select-options="{ enabled: true , clearSelectionText: '', }" @on-selected-rows-change="selectionChanged" :pagination-options="{ enabled: true, mode: 'records', nextLabel: 'next', prevLabel: 'prev', }" :styleClass="showDropdown?'tableOne table-hover vgt-table full-height':'tableOne table-hover vgt-table non-height'" > <div slot="selected-row-actions"> <button class="btn btn-danger btn-sm" @click="delete_by_selected()">{{$t('Del')}}</button> </div> <div slot="table-actions" class="mt-2 mb-3"> <b-button variant="outline-info ripple m-1" size="sm" v-b-toggle.sidebar-right> <i class="i-Filter-2"></i> {{ $t("Filter") }} </b-button> <b-button @click="Quotation_PDF()" size="sm" variant="outline-success ripple m-1"> <i class="i-File-Copy"></i> PDF </b-button> <vue-excel-xlsx class="btn btn-sm btn-outline-danger ripple m-1" :data="quotations" :columns="columns" :file-name="'quotations'" :file-type="'xlsx'" :sheet-name="'quotations'" > <i class="i-File-Excel"></i> EXCEL </vue-excel-xlsx> <router-link class="btn-sm btn btn-primary ripple btn-icon m-1" v-if="currentUserPermissions && currentUserPermissions.includes('Quotations_add')" to="/app/quotations/store" > <span class="ul-btn__icon"> <i class="i-Add"></i> </span> <span class="ul-btn__text ml-1">{{$t('Add')}}</span> </router-link> </div> <template slot="table-row" slot-scope="props"> <span v-if="props.column.field == 'actions'"> <div> <b-dropdown id="dropdown-left" variant="link" text="Left align" toggle-class="text-decoration-none" size="lg" no-caret > <template v-slot:button-content class="_r_btn border-0"> <span class="_dot _r_block-dot bg-dark"></span> <span class="_dot _r_block-dot bg-dark"></span> <span class="_dot _r_block-dot bg-dark"></span> </template> <b-navbar-nav> <b-dropdown-item title="Show" :to="'/app/quotations/detail/'+props.row.id"> <i class="nav-icon i-Eye font-weight-bold mr-2"></i> {{$t('DetailQuote')}} </b-dropdown-item> </b-navbar-nav> <b-dropdown-item title="Edit" v-if="currentUserPermissions.includes('Quotations_edit')" :to="'/app/quotations/edit/'+props.row.id" > <i class="nav-icon i-Pen-2 font-weight-bold mr-2"></i> {{$t('EditQuote')}} </b-dropdown-item> <b-dropdown-item title="Create Sale" v-if="currentUserPermissions.includes('Quotations_edit')" :to="'/app/quotations/Create_sale/'+props.row.id" > <i class="nav-icon i-Add font-weight-bold mr-2"></i> {{$t('CreateSale')}} </b-dropdown-item> <b-dropdown-item title="PDF" @click="Quote_pdf(props.row , props.row.id)"> <i class="nav-icon i-File-TXT font-weight-bold mr-2"></i> {{$t('DownloadPdf')}} </b-dropdown-item> <b-dropdown-item title=" WhatsApp Notification" @click="Send_WhatsApp(props.row.id)"> <i class="nav-icon i-Envelope-2 font-weight-bold mr-2"></i> WhatsApp Notification </b-dropdown-item> <b-dropdown-item title="Email" @click="SendEmail(props.row.id)"> <i class="nav-icon i-Envelope-2 font-weight-bold mr-2"></i> {{$t('email_notification')}} </b-dropdown-item> <b-dropdown-item title="SMS" @click="Quote_SMS(props.row.id)"> <i class="nav-icon i-Speach-Bubble font-weight-bold mr-2"></i> {{$t('sms_notification')}} </b-dropdown-item> <b-dropdown-item title="Delete" v-if="currentUserPermissions.includes('Quotations_delete')" @click="Remove_Quotation(props.row.id)" > <i class="nav-icon i-Close-Window font-weight-bold mr-2"></i> {{$t('DeleteQuote')}} </b-dropdown-item> </b-dropdown> </div> </span> <div v-else-if="props.column.field == 'statut'"> <span v-if="props.row.statut == 'sent'" class="badge badge-outline-success" >{{$t('Sent')}}</span> <span v-else class="badge badge-outline-info">{{$t('Pending')}}</span> </div> <div v-else-if="props.column.field == 'Ref'"> <router-link :to="'/app/quotations/detail/'+props.row.id" > <span class="ul-btn__text ml-1">{{props.row.Ref}}</span> </router-link> </div> </template> </vue-good-table> </div> <b-sidebar id="sidebar-right" :title="$t('Filter')" bg-variant="white" right shadow> <div class="px-3 py-2"> <b-row> <!-- date --> <b-col md="12"> <b-form-group :label="$t('date')"> <b-form-input type="date" v-model="Filter_date"></b-form-input> </b-form-group> </b-col> <!-- Reference --> <b-col md="12"> <b-form-group :label="$t('Reference')"> <b-form-input label="Reference" :placeholder="$t('Reference')" v-model="Filter_Ref"></b-form-input> </b-form-group> </b-col> <!-- Customer --> <b-col md="12"> <b-form-group :label="$t('Customer')"> <v-select :reduce="label => label.value" :placeholder="$t('Choose_Customer')" v-model="Filter_client" :options="customers.map(customers => ({label: customers.name, value: customers.id}))" /> </b-form-group> </b-col> <!-- warehouse --> <b-col md="12"> <b-form-group :label="$t('warehouse')"> <v-select v-model="Filter_warehouse" :reduce="label => label.value" :placeholder="$t('Choose_Warehouse')" :options="warehouses.map(warehouses => ({label: warehouses.name, value: warehouses.id}))" /> </b-form-group> </b-col> <!-- Status --> <b-col md="12"> <b-form-group :label="$t('Status')"> <v-select v-model="Filter_status" :reduce="label => label.value" :placeholder="$t('Choose_Status')" :options=" [ {label: 'Sent', value: 'sent'}, {label: 'Pending', value: 'pending'} ]" ></v-select> </b-form-group> </b-col> <b-col md="6" sm="12"> <b-button @click="Get_Quotations(serverParams.page)" variant="primary ripple m-1" size="sm" block > <i class="i-Filter-2"></i> {{ $t("Filter") }} </b-button> </b-col> <b-col md="6" sm="12"> <b-button @click="Reset_Filter()" variant="danger ripple m-1" size="sm" block> <i class="i-Power-2"></i> {{ $t("Reset") }} </b-button> </b-col> </b-row> </div> </b-sidebar> </div> </template> <script> import { mapActions, mapGetters } from "vuex"; import NProgress from "nprogress"; import jsPDF from "jspdf"; import "jspdf-autotable"; export default { metaInfo: { title: "Quotation" }, data() { return { isLoading: true, serverParams: { sort: { field: "id", type: "desc" }, page: 1, perPage: 10 }, selectedIds: [], totalRows: "", search: "", showDropdown: false, Filter_date: "", Filter_client: "", Filter_status: "", Filter_Ref: "", Filter_warehouse: "", customers: [], warehouses: [], details: [], quotations: [], quote: {}, limit: "10", email: { to: "", subject: "", message: "", client_name: "", quote_Ref: "" } }; }, mounted: function() { this.$root.$on("bv::dropdown::show", bvEvent => { this.showDropdown = true; }); this.$root.$on("bv::dropdown::hide", bvEvent => { this.showDropdown = false; }); }, computed: { ...mapGetters(["currentUserPermissions", "currentUser"]), columns() { return [ { label: this.$t("date"), field: "date", tdClass: "text-left", thClass: "text-left" }, { label: this.$t("Reference"), field: "Ref", tdClass: "text-left", thClass: "text-left" }, { label: this.$t("Customer"), field: "client_name", tdClass: "text-left", thClass: "text-left" }, { label: this.$t("warehouse"), field: "warehouse_name", tdClass: "text-left", thClass: "text-left" }, { label: this.$t("Status"), field: "statut", html: true, tdClass: "text-left", thClass: "text-left" }, { label: this.$t("Total"), field: "GrandTotal", type: "decimal", tdClass: "text-left", thClass: "text-left" }, { label: this.$t("Action"), field: "actions", html: true, tdClass: "text-right", thClass: "text-right", sortable: false } ]; } }, methods: { updateParams(newProps) { this.serverParams = Object.assign({}, this.serverParams, newProps); }, //---- Event Page Change onPageChange({ currentPage }) { if (this.serverParams.page !== currentPage) { this.updateParams({ page: currentPage }); this.Get_Quotations(currentPage); } }, //---- Event Per Page Change onPerPageChange({ currentPerPage }) { if (this.limit !== currentPerPage) { this.limit = currentPerPage; this.updateParams({ page: 1, perPage: currentPerPage }); this.Get_Quotations(1); } }, //---- Event Select Rows selectionChanged({ selectedRows }) { this.selectedIds = []; selectedRows.forEach((row, index) => { this.selectedIds.push(row.id); }); }, //---- Event Sort onSortChange(params) { let field = ""; if (params[0].field == "client_name") { field = "client_id"; } else if (params[0].field == "warehouse_name") { field = "warehouse_id"; } else { field = params[0].field; } this.updateParams({ sort: { type: params[0].type, field: field } }); this.Get_Quotations(this.serverParams.page); }, //---- Event Search onSearch(value) { this.search = value.searchTerm; this.Get_Quotations(this.serverParams.page); }, //------ Toast makeToast(variant, msg, title) { this.$root.$bvToast.toast(msg, { title: title, variant: variant, solid: true }); }, //------ Reset Filter Reset_Filter() { this.search = ""; this.Filter_date = ""; this.Filter_client = ""; this.Filter_status = ""; this.Filter_Ref = ""; this.Filter_warehouse = ""; this.Get_Quotations(this.serverParams.page); }, //------------------------------------- Quotations PDF -------------------------\\ Quotation_PDF() { var self = this; let pdf = new jsPDF("p", "pt"); const fontPath = "/fonts/Vazirmatn-Bold.ttf"; pdf.addFont(fontPath, "VazirmatnBold", "bold"); pdf.setFont("VazirmatnBold"); let columns = [ { title: self.$t("date"), dataKey: "date" }, { title: self.$t("Reference"), dataKey: "Ref" }, { title: self.$t("Customer"), dataKey: "client_name" }, { title: self.$t("warehouse"), dataKey: "warehouse_name" }, { title: self.$t("Status"), dataKey: "statut" }, { title: self.$t("Total"), dataKey: "GrandTotal" } ]; // Calculate totals let totalGrandTotal = self.quotations.reduce((sum, quotation) => sum + parseFloat(quotation.GrandTotal || 0), 0); let footer = [{ date: self.$t("Total"), Ref: '', client_name: '', warehouse_name: '', statut: '', GrandTotal: `${totalGrandTotal.toFixed(2)}`, }]; pdf.autoTable({ columns: columns, body: self.quotations, foot: footer, startY: 70, theme: "grid", didDrawPage: (data) => { pdf.setFont("VazirmatnBold"); pdf.setFontSize(18); pdf.text("Quotation List", 40, 25); }, styles: { font: "VazirmatnBold", halign: "center", // }, headStyles: { fillColor: [200, 200, 200], textColor: [0, 0, 0], fontStyle: "bold", }, footStyles: { fillColor: [230, 230, 230], textColor: [0, 0, 0], fontStyle: "bold", }, }); pdf.save("Quotation_List.pdf"); }, Send_WhatsApp(id) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .post("quotation_send_whatsapp", { id: id, }) .then(response => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); var phone = response.data.phone; var message = response.data.message; // Encode phone number and message var encodedPhone = encodeURIComponent(phone); var encodedMessage = encodeURIComponent(message); // Create WhatsApp URL var whatsappUrl = `https://web.whatsapp.com/send/?phone=${encodedPhone}&text=${encodedMessage}`; // Open the WhatsApp URL in a new window window.open(whatsappUrl, '_blank'); }) .catch(error => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.makeToast("danger", "Failed to send the Message", this.$t("Failed")); }); }, //----------------------------------- Quotation PDF by id -------------------------\\ Quote_pdf(quote, id) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .get("quote_pdf/" + id, { responseType: "blob", // important headers: { "Content-Type": "application/json" } }) .then(response => { const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement("a"); link.href = url; link.setAttribute("download", "Quotation_" + quote.Ref + ".pdf"); document.body.appendChild(link); link.click(); // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); }) .catch(() => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); }); }, SendEmail(id) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .post("quotations_send_email", { id: id, }) .then(response => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.makeToast( "success", this.$t("Send.TitleEmail"), this.$t("Success") ); }) .catch(error => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.makeToast("danger", this.$t("SMTPIncorrect"), this.$t("Failed")); }); }, //---------SMS notification Quote_SMS(id) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .post("quotations_send_sms", { id: id, }) .then(response => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.makeToast( "success", this.$t("Send_SMS"), this.$t("Success") ); }) .catch(error => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.makeToast("danger", this.$t("sms_config_invalid"), this.$t("Failed")); }); }, //---------------------------------------- Set To Strings-------------------------\\ setToStrings() { // Simply replaces null values with strings=''s if (this.Filter_client === null) { this.Filter_client = ""; } else if (this.Filter_warehouse === null) { this.Filter_warehouse = ""; } else if (this.Filter_status === null) { this.Filter_status = ""; } }, //---------------------------------------- Get All Quotations -------------------------\\ Get_Quotations(page) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); this.setToStrings(); axios .get( "quotations?page=" + this.serverParams.page + "&Ref=" + this.Filter_Ref + "&client_id=" + this.Filter_client + "&statut=" + this.Filter_status + "&warehouse_id=" + this.Filter_warehouse + "&date=" + this.Filter_date + "&SortField=" + this.serverParams.sort.field + "&SortType=" + this.serverParams.sort.type + "&search=" + this.search + "&limit=" + this.limit ) .then(response => { this.quotations = response.data.quotations; this.customers = response.data.customers; this.warehouses = response.data.warehouses; this.totalRows = response.data.totalRows; // Complete the animation of theprogress bar. NProgress.done(); this.isLoading = false; }) .catch(response => { // Complete the animation of theprogress bar. NProgress.done(); setTimeout(() => { this.isLoading = false; }, 500); }); }, //-------------------------------------------- Delete Quotation -------------------------\\ Remove_Quotation(id) { this.$swal({ title: this.$t("Delete_Title"), text: this.$t("Delete_Text"), type: "warning", showCancelButton: true, confirmButtonColor: "#3085d6", cancelButtonColor: "#d33", cancelButtonText: this.$t("Delete_cancelButtonText"), confirmButtonText: this.$t("Delete_confirmButtonText") }).then(result => { if (result.value) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .delete("quotations/" + id) .then(() => { this.$swal( this.$t("Delete_Deleted"), this.$t("Deleted_in_successfully"), "success" ); Fire.$emit("Delete_Quote"); }) .catch(() => { // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); this.$swal( this.$t("Delete_Failed"), this.$t("Delete_Therewassomethingwronge"), "warning" ); }); } }); }, //---- Delete quotations by selection delete_by_selected() { this.$swal({ title: this.$t("Delete_Title"), text: this.$t("Delete_Text"), type: "warning", showCancelButton: true, confirmButtonColor: "#3085d6", cancelButtonColor: "#d33", cancelButtonText: this.$t("Delete_cancelButtonText"), confirmButtonText: this.$t("Delete_confirmButtonText") }).then(result => { if (result.value) { // Start the progress bar. NProgress.start(); NProgress.set(0.1); axios .post("quotations_delete_by_selection", { selectedIds: this.selectedIds }) .then(() => { this.$swal( this.$t("Delete_Deleted"), this.$t("Deleted_in_successfully"), "success" ); Fire.$emit("Delete_Quote"); }) .catch(() => { // Complete the animation of theprogress bar. setTimeout(() => NProgress.done(), 500); this.$swal( this.$t("Delete_Failed"), this.$t("Delete_Therewassomethingwronge"), "warning" ); }); } }); } }, //-----------------------------Autoload function------------------- created: function() { this.Get_Quotations(1); Fire.$on("Delete_Quote", () => { setTimeout(() => { this.Get_Quotations(this.serverParams.page); // Complete the animation of the progress bar. setTimeout(() => NProgress.done(), 500); }, 500); }); } }; </script>