initial commit
This commit is contained in:
134
resources/js/views/apps/invoice/AddOrderCart.vue
Normal file
134
resources/js/views/apps/invoice/AddOrderCart.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import CartProducts from './CartProducts.vue';
|
||||
|
||||
const props = defineProps({
|
||||
page: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'push',
|
||||
'remove',
|
||||
'updateGrandTotal',
|
||||
'updateShippingTotal',
|
||||
]);
|
||||
|
||||
const invoice = ref(props.data.invoice);
|
||||
const salesperson = ref(props.data.salesperson);
|
||||
const thanksNote = ref(props.data.thanksNote);
|
||||
const note = ref(props.data.note);
|
||||
|
||||
const productTotals = ref({});
|
||||
const productShippingTotals = ref({});
|
||||
|
||||
|
||||
const removeProduct = (id) => {
|
||||
emit('remove', id);
|
||||
delete productTotals.value[id];
|
||||
delete productShippingTotals.value[id];
|
||||
calculateGrandTotal();
|
||||
calculateShippingTotal();
|
||||
};
|
||||
const getData = computed(() => {
|
||||
console.log(">>>>>Asid",props.data.purchasedProducts);
|
||||
});
|
||||
const updateProductTotal = ({ id, data, total, totalShipping }) => {
|
||||
console.log('Updating totals for product:', data,id, data, total, totalShipping);
|
||||
emit('push', data);
|
||||
productTotals.value[id] = total;
|
||||
productShippingTotals.value[id] = totalShipping;
|
||||
calculateGrandTotal();
|
||||
calculateShippingTotal();
|
||||
};
|
||||
|
||||
const calculateGrandTotal = () => {
|
||||
const grandTotal = Object.values(productTotals.value).reduce((sum, total) => sum + total, 0);
|
||||
emit('updateGrandTotal', grandTotal);
|
||||
};
|
||||
|
||||
const calculateShippingTotal = () => {
|
||||
const shippingTotal = Object.values(productShippingTotals.value).reduce((sum, totalShipping) => sum + totalShipping, 0);
|
||||
emit('updateShippingTotal', shippingTotal);
|
||||
};
|
||||
|
||||
const grandTotal = computed(() => {
|
||||
return Object.values(productTotals.value).reduce((sum, total) => sum + total, 0);
|
||||
});
|
||||
|
||||
const shippingTotal = computed(() => {
|
||||
return Object.values(productShippingTotals.value).reduce((sum, totalShipping) => sum + totalShipping, 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
<div v-if="getData"></div>
|
||||
<!-- 👉 Add purchased products -->
|
||||
<div class="add-products-form">
|
||||
|
||||
<div v-for="(product, index) in props.data.purchasedProducts" :key="product.id || index" class="mb-4">
|
||||
<CartProducts :id="product.id" :data="product" @remove-product="removeProduct"
|
||||
@update-total="updateProductTotal" :page="page"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
|
||||
<!-- 👉 Total Amount -->
|
||||
<div class="d-flex justify-space-between flex-wrap flex-column flex-sm-row">
|
||||
<div class="mb-6 mb-sm-0">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Subtotal:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
${{ grandTotal.toFixed(2) }}
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Shipping Total:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
${{ shippingTotal.toFixed(2) }}
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<VDivider class="mt-4 mb-3" />
|
||||
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Total:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
${{ (grandTotal + shippingTotal).toFixed(2) }}
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
275
resources/js/views/apps/invoice/CartProducts.vue
Normal file
275
resources/js/views/apps/invoice/CartProducts.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
const store = useStore();
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
page: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '',
|
||||
title: '',
|
||||
subscription: null,
|
||||
// onetime: null,
|
||||
price: 0,
|
||||
qty: 0,
|
||||
shipping_cost: 0,
|
||||
is_prescription_required:'',
|
||||
}),
|
||||
},
|
||||
});
|
||||
const localProductData = ref([{
|
||||
id: Date.now(),
|
||||
title: '',
|
||||
subscription: null,
|
||||
// onetime: null,
|
||||
price: 0,
|
||||
qty: 1,
|
||||
shipping_cost: 0,
|
||||
is_prescription_required:null,
|
||||
}]);
|
||||
const getA = computed(() => {
|
||||
console.log("hello asif",props.id, props.data);
|
||||
});
|
||||
const subscriptions = [
|
||||
{ title: 'Yes', value: "Yes" },
|
||||
{ title: 'No', value: "No" },
|
||||
];
|
||||
const onetime = [
|
||||
{ title: 'Yes', value: "Yes" },
|
||||
{ title: 'No', value: "No" },
|
||||
];
|
||||
|
||||
|
||||
const addNewItem = () => {
|
||||
localProductData.value.push({
|
||||
id: Date.now(),
|
||||
title: '',
|
||||
subscription: null,
|
||||
// onetime: null,
|
||||
price: 0,
|
||||
qty: 1,
|
||||
shipping_cost: 0,
|
||||
is_prescription_required:null,
|
||||
});
|
||||
};
|
||||
const emit = defineEmits([
|
||||
'removeProduct',
|
||||
'updateTotal',
|
||||
]);
|
||||
|
||||
const productData = ref([]);
|
||||
const selectedItem = ref(null);
|
||||
|
||||
|
||||
const useSortedProduct = () => {
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
const productData = ref([]);
|
||||
|
||||
const sortedProduct = computed(() => {
|
||||
return productData.value
|
||||
.slice()
|
||||
.filter(product => product.title !== 'Select Any')
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
});
|
||||
|
||||
const fetchProductData = async () => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
await store.dispatch('getAllProductsList');
|
||||
productData.value = store.getters.getProductsList || [];
|
||||
console.log('Fetched Product data:', productData.value);
|
||||
} catch (e) {
|
||||
console.error('Error fetching Product data:', e);
|
||||
error.value = 'Failed to fetch Product data';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(fetchProductData);
|
||||
|
||||
return { sortedProduct, isLoading, error, fetchProductData };
|
||||
};
|
||||
|
||||
|
||||
|
||||
const { sortedProduct, isLoading, error } = useSortedProduct();
|
||||
|
||||
|
||||
watch(selectedItem, () => {
|
||||
const item = sortedProduct.value.find(obj => obj.title === selectedItem.value);
|
||||
if (item && item.id !== '') {
|
||||
const newProduct = {
|
||||
id: item.id || Date.now(),
|
||||
title: item.title,
|
||||
subscription: item.subscription === 'true' ? true : false,
|
||||
// onetime: item.onetime === 'true' ? true : false,
|
||||
price: item.price || 0,
|
||||
qty: 1,
|
||||
shipping_cost: item.shipping_cost || 0,
|
||||
is_prescription_required:item.is_prescription_required || 0,
|
||||
};
|
||||
localProductData.value.push(newProduct);
|
||||
updateTotal();
|
||||
selectedItem.value = null; // Reset selection after adding
|
||||
}
|
||||
});
|
||||
|
||||
const removeProduct2 = () => {
|
||||
emit('removeProduct', props.id);
|
||||
};
|
||||
const removeProduct = (index) => {
|
||||
localProductData.value.splice(index, 1);
|
||||
//emit('removeProduct', props.id);
|
||||
updateTotal();
|
||||
};
|
||||
const totalPrice = computed(() =>
|
||||
localProductData.value.reduce((sum, product) =>
|
||||
sum + Number(product.price) * Number(product.qty), 0)
|
||||
);
|
||||
|
||||
const totalShipping = computed(() =>
|
||||
localProductData.value.reduce((sum, product) => {
|
||||
const shippingCost = Number(product.shipping_cost);
|
||||
const qty = Number(product.qty);
|
||||
return sum + (shippingCost > 0 ? shippingCost * qty : 0);
|
||||
}, 0)
|
||||
);
|
||||
|
||||
const updateTotal = () => {
|
||||
console.log('totalShipping.value',totalPrice.value,totalShipping.value)
|
||||
emit('updateTotal', {
|
||||
id: props.id,
|
||||
data: localProductData.value,
|
||||
total: totalPrice.value,
|
||||
totalShipping: totalShipping.value
|
||||
});
|
||||
};
|
||||
|
||||
watch(() => localProductData.value.qty, updateTotal);
|
||||
watch(() => localProductData.value.price, updateTotal);
|
||||
const updateProductDetails = (index) => {
|
||||
const selectedProduct = sortedProduct.value.find(item => item.title === localProductData.value[index].title);
|
||||
if (selectedProduct) {
|
||||
localProductData.value[index] = {
|
||||
...localProductData.value[index],
|
||||
id: selectedProduct.id || Date.now(),
|
||||
price: selectedProduct.price || 0,
|
||||
shipping_cost: selectedProduct.shipping_cost || 0,
|
||||
is_prescription_required:selectedProduct.is_prescription_required|| 0
|
||||
};
|
||||
updateTotal();
|
||||
}
|
||||
};
|
||||
// Emit initial total on component mount
|
||||
onBeforeMount(() => {
|
||||
updateTotal();
|
||||
});
|
||||
watch(localProductData, () => {
|
||||
updateTotal();
|
||||
}, { deep: true });
|
||||
watch(() => store.getters.getPatientOrderDetail, (newVal) => {
|
||||
if (newVal && newVal.order_details && newVal.order_details.id) {
|
||||
setData(newVal);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
function setData(orderDetail) {
|
||||
if(props.page === 'edit'){
|
||||
const storedProducts = orderDetail.order_items.items
|
||||
console.log('storedProducts>>>>>>>',storedProducts);
|
||||
localProductData.value = storedProducts.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
subscription: item.subscription === true? 'Yes':'No',
|
||||
// onetime: item.onetime === true? 'Yes':'No',
|
||||
price: parseFloat(item.price),
|
||||
qty: parseInt(item.quantity, 10),
|
||||
shipping_cost: parseFloat(item.shipping_cost),
|
||||
is_prescription_required:item.is_prescription_required,
|
||||
}));
|
||||
updateTotal();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="getA"></div>
|
||||
<div class="add-products-header mb-2 d-none d-md-flex mb-4">
|
||||
<VRow class="me-10">
|
||||
<VCol cols="12" md="3">
|
||||
<h6 class="text-h6">Item</h6>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<h6 class="text-h6">Subscription</h6>
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<h6 class="text-h6 ps-2">Price</h6>
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<h6 class="text-h6 ps-2">QTY</h6>
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<h6 class="text-h6">Total</h6>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<div v-for="(product, index) in localProductData" :key="product.id">
|
||||
<VCard flat border class="d-flex flex-sm-row flex-column-reverse mb-4">
|
||||
<div class="pa-5 flex-grow-1">
|
||||
<VRow>
|
||||
<VCol cols="12" md="3">
|
||||
<VAutocomplete v-model="product.title" :items="sortedProduct" item-title="title" item-value="title"
|
||||
label="Select Item" placeholder="Select Item" :loading="isLoading" :error-messages="error"
|
||||
:rules="[v => !!v || 'Item is required']" @update:modelValue="updateProductDetails(index)" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VAutocomplete v-model="product.subscription" :items="subscriptions" item-title="title" item-value="value"
|
||||
label="Select Subscription" placeholder="Select Subscription" :error-messages="error"
|
||||
:rules="[v => !!v || 'Subscription is required']" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<VTextField v-model.number="product.price" type="number" label="Price" placeholder="100" disabled />
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<VTextField v-model.number="product.qty" type="number" label="Quantity" placeholder="1" :rules="[
|
||||
v => !!v || 'Quantity is required',
|
||||
v => v > 0 || 'Quantity must be greater than 0'
|
||||
]" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<p class="text-h6 mt-4">
|
||||
${{ (Number(product.price) * Number(product.qty)).toFixed(2) }}
|
||||
</p>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-end item-actions pa-2"
|
||||
:class="$vuetify.display.smAndUp ? 'border-s' : 'border-b'">
|
||||
<VBtn icon @click="removeProduct(index)">
|
||||
<VIcon icon="ri-close-line" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
|
||||
<!-- Add new item button -->
|
||||
<VBtn size="small" prepend-icon="ri-add-line" @click="addNewItem">
|
||||
Add Item
|
||||
</VBtn>
|
||||
|
||||
</template>
|
126
resources/js/views/apps/invoice/InvoiceAddPaymentDrawer.vue
Normal file
126
resources/js/views/apps/invoice/InvoiceAddPaymentDrawer.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
|
||||
const props = defineProps({
|
||||
isDrawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:isDrawerOpen',
|
||||
'submit',
|
||||
])
|
||||
|
||||
const invoiceBalance = ref()
|
||||
const paymentAmount = ref()
|
||||
const paymentDate = ref('')
|
||||
const paymentMethod = ref()
|
||||
const paymentNote = ref('')
|
||||
|
||||
const onSubmit = () => {
|
||||
emit('update:isDrawerOpen', false)
|
||||
emit('submit', {
|
||||
invoiceBalance: invoiceBalance.value,
|
||||
paymentAmount: paymentAmount.value,
|
||||
paymentDate: paymentDate.value,
|
||||
paymentMethod: paymentMethod.value,
|
||||
paymentNote: paymentNote.value,
|
||||
})
|
||||
}
|
||||
|
||||
const handleDrawerModelValueUpdate = val => {
|
||||
emit('update:isDrawerOpen', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VNavigationDrawer
|
||||
temporary
|
||||
location="end"
|
||||
:width="400"
|
||||
:model-value="props.isDrawerOpen"
|
||||
class="scrollable-content"
|
||||
@update:model-value="handleDrawerModelValueUpdate"
|
||||
>
|
||||
<!-- 👉 Header -->
|
||||
<AppDrawerHeaderSection
|
||||
title="Add Payment"
|
||||
@cancel="$emit('update:isDrawerOpen', false)"
|
||||
/>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<PerfectScrollbar :options="{ wheelPropagation: false }">
|
||||
<VCard flat>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="onSubmit">
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="invoiceBalance"
|
||||
label="Invoice Balance"
|
||||
type="number"
|
||||
placeholder="$99"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="paymentAmount"
|
||||
label="Payment Amount"
|
||||
type="number"
|
||||
placeholder="$99"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<AppDateTimePicker
|
||||
v-model="paymentDate"
|
||||
label="Payment Date"
|
||||
placeholder="Select Date"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VSelect
|
||||
v-model="paymentMethod"
|
||||
label="Select Payment Method"
|
||||
placeholder="Select Payment Method"
|
||||
:items="['Cash', 'Bank Transfer', 'Debit', 'Credit', 'PayPal']"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="paymentNote"
|
||||
label="Internal Payment Note"
|
||||
placeholder="Internal Payment Note"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
type="submit"
|
||||
class="me-3"
|
||||
>
|
||||
Send
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
type="reset"
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
@click="$emit('update:isDrawerOpen', false)"
|
||||
>
|
||||
Cancel
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</PerfectScrollbar>
|
||||
</VNavigationDrawer>
|
||||
</template>
|
334
resources/js/views/apps/invoice/InvoiceEditable.vue
Normal file
334
resources/js/views/apps/invoice/InvoiceEditable.vue
Normal file
@@ -0,0 +1,334 @@
|
||||
<script setup>
|
||||
import InvoiceProductEdit from './InvoiceProductEdit.vue'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: null,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'push',
|
||||
'remove',
|
||||
])
|
||||
|
||||
const invoice = ref(props.data.invoice)
|
||||
const salesperson = ref(props.data.salesperson)
|
||||
const thanksNote = ref(props.data.thanksNote)
|
||||
const note = ref(props.data.note)
|
||||
|
||||
// 👉 Clients
|
||||
const clients = ref([])
|
||||
|
||||
// 👉 fetchClients
|
||||
const fetchClients = async () => {
|
||||
const { data, error } = await useApi('/apps/invoice/clients')
|
||||
if (error.value)
|
||||
console.log(error.value)
|
||||
else
|
||||
clients.value = data.value
|
||||
}
|
||||
|
||||
fetchClients()
|
||||
|
||||
// 👉 Add item function
|
||||
const addItem = () => {
|
||||
emit('push', {
|
||||
title: 'App Design',
|
||||
cost: 24,
|
||||
hours: 1,
|
||||
description: 'Designed UI kit & app pages.',
|
||||
})
|
||||
}
|
||||
|
||||
const removeProduct = id => {
|
||||
emit('remove', id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard class="pa-12">
|
||||
<!-- SECTION Header -->
|
||||
<div class="d-flex flex-wrap justify-space-between flex-column rounded bg-var-theme-background flex-sm-row gap-6 pa-6 mb-6">
|
||||
<!-- 👉 Left Content -->
|
||||
<div>
|
||||
<div class="d-flex align-center mb-6">
|
||||
<!-- 👉 Logo -->
|
||||
<VNodeRenderer
|
||||
:nodes="themeConfig.app.logo"
|
||||
class="me-3"
|
||||
/>
|
||||
|
||||
<!-- 👉 Title -->
|
||||
<h6 class="font-weight-medium text-xl text-uppercase">
|
||||
{{ themeConfig.app.title }}
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Address -->
|
||||
<p class="text-high-emphasis mb-0">
|
||||
Office 149, 450 South Brand Brooklyn
|
||||
</p>
|
||||
<p class="text-high-emphasis mb-0">
|
||||
San Diego County, CA 91905, USA
|
||||
</p>
|
||||
<p class="text-high-emphasis mb-0">
|
||||
+1 (123) 456 7891, +44 (876) 543 2198
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Right Content -->
|
||||
<div>
|
||||
<!-- 👉 Invoice Id -->
|
||||
<div class="d-flex align-start font-weight-medium justify-sm-end flex-column flex-sm-row text-lg mb-3">
|
||||
<span
|
||||
class="text-high-emphasis me-4"
|
||||
style="inline-size: 5.625rem ;"
|
||||
>Invoice:</span>
|
||||
<span>
|
||||
<VTextField
|
||||
v-model="invoice.id"
|
||||
disabled
|
||||
density="compact"
|
||||
prefix="#"
|
||||
style="inline-size: 9.5rem;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Issue Date -->
|
||||
<div class="d-flex align-start justify-sm-end flex-column flex-sm-row mb-3">
|
||||
<span
|
||||
class="text-high-emphasis me-4"
|
||||
style="inline-size: 5.625rem;"
|
||||
>Date Issued:</span>
|
||||
|
||||
<span style="inline-size: 9.5rem;">
|
||||
<AppDateTimePicker
|
||||
v-model="invoice.issuedDate"
|
||||
density="compact"
|
||||
placeholder="YYYY-MM-DD"
|
||||
:config="{ position: 'auto right' }"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Due Date -->
|
||||
<div class="d-flex align-start justify-sm-end flex-column flex-sm-row mb-0">
|
||||
<span
|
||||
class="text-high-emphasis me-4"
|
||||
style="inline-size: 5.625rem;"
|
||||
>Due Date:</span>
|
||||
<span style="min-inline-size: 9.5rem;">
|
||||
<AppDateTimePicker
|
||||
v-model="invoice.dueDate"
|
||||
density="compact"
|
||||
placeholder="YYYY-MM-DD"
|
||||
:config="{ position: 'auto right' }"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !SECTION -->
|
||||
|
||||
<VRow>
|
||||
<VCol class="text-no-wrap">
|
||||
<h6 class="text-h6 mb-4">
|
||||
Invoice To:
|
||||
</h6>
|
||||
|
||||
<VSelect
|
||||
v-model="invoice.client"
|
||||
:items="clients"
|
||||
item-title="name"
|
||||
item-value="name"
|
||||
placeholder="Select Client"
|
||||
return-object
|
||||
class="mb-4"
|
||||
style="inline-size: 11.875rem;"
|
||||
/>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.name }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.company }}
|
||||
</p>
|
||||
<p
|
||||
v-if="invoice.client.address"
|
||||
class="mb-0"
|
||||
>
|
||||
{{ invoice.client.address }}, {{ invoice.client.country }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.contact }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.companyEmail }}
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
<VCol class="text-no-wrap">
|
||||
<h6 class="text-h6 mb-4">
|
||||
Bill To:
|
||||
</h6>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-6">
|
||||
Total Due:
|
||||
</td>
|
||||
<td>{{ props.data.paymentDetails.totalDue }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-6">
|
||||
Bank Name:
|
||||
</td>
|
||||
<td>{{ props.data.paymentDetails.bankName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-6">
|
||||
Country:
|
||||
</td>
|
||||
<td>{{ props.data.paymentDetails.country }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-6">
|
||||
IBAN:
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-wrap me-4">
|
||||
{{ props.data.paymentDetails.iban }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-6">
|
||||
SWIFT Code:
|
||||
</td>
|
||||
<td>{{ props.data.paymentDetails.swiftCode }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
<!-- 👉 Add purchased products -->
|
||||
<div class="add-products-form">
|
||||
<div
|
||||
v-for="(product, index) in props.data.purchasedProducts"
|
||||
:key="product.title"
|
||||
class="mb-4"
|
||||
>
|
||||
<InvoiceProductEdit
|
||||
:id="index"
|
||||
:data="product"
|
||||
@remove-product="removeProduct"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
size="small"
|
||||
prepend-icon="ri-add-line"
|
||||
@click="addItem"
|
||||
>
|
||||
Add Item
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
|
||||
<!-- 👉 Total Amount -->
|
||||
<div class="d-flex justify-space-between flex-wrap flex-column flex-sm-row">
|
||||
<div class="mb-6 mb-sm-0">
|
||||
<div class="d-flex align-center mb-4">
|
||||
<h6 class="text-h6 me-2">
|
||||
Salesperson:
|
||||
</h6>
|
||||
<VTextField
|
||||
v-model="salesperson"
|
||||
style="inline-size: 8rem;"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VTextField
|
||||
v-model="thanksNote"
|
||||
placeholder="Thanks for your business"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Subtotal:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
$1800
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Discount:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
$28
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Tax:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
21%
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<VDivider class="mt-4 mb-3" />
|
||||
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Total:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-h6">
|
||||
$1690
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
|
||||
<div>
|
||||
<h6 class="text-h6 mb-2">
|
||||
Note:
|
||||
</h6>
|
||||
<VTextarea
|
||||
v-model="note"
|
||||
placeholder="Write note here..."
|
||||
:rows="2"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
207
resources/js/views/apps/invoice/InvoiceProductEdit.vue
Normal file
207
resources/js/views/apps/invoice/InvoiceProductEdit.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
title: 'App Design',
|
||||
cost: 24,
|
||||
hours: 1,
|
||||
description: 'Designed UI kit & app pages.',
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'removeProduct',
|
||||
'totalAmount',
|
||||
])
|
||||
|
||||
const itemsOptions = [
|
||||
{
|
||||
title: 'App Design',
|
||||
cost: 24,
|
||||
hours: 1,
|
||||
description: 'Designed UI kit & app pages.',
|
||||
},
|
||||
{
|
||||
title: 'App Customization',
|
||||
cost: 26,
|
||||
hours: 1,
|
||||
description: 'Customization & Bug Fixes.',
|
||||
},
|
||||
{
|
||||
title: 'ABC Template',
|
||||
cost: 28,
|
||||
hours: 1,
|
||||
description: 'Vuetify admin template.',
|
||||
},
|
||||
{
|
||||
title: 'App Development',
|
||||
cost: 32,
|
||||
hours: 1,
|
||||
description: 'Native App Development.',
|
||||
},
|
||||
]
|
||||
|
||||
const selectedItem = ref('App Customization')
|
||||
const localProductData = ref(structuredClone(toRaw(props.data)))
|
||||
|
||||
watch(selectedItem, () => {
|
||||
const item = itemsOptions.filter(obj => {
|
||||
return obj.title === selectedItem.value
|
||||
})
|
||||
|
||||
localProductData.value = item[0]
|
||||
})
|
||||
|
||||
const removeProduct = () => {
|
||||
emit('removeProduct', props.id)
|
||||
}
|
||||
|
||||
const totalPrice = computed(() => Number(localProductData.value.cost) * Number(localProductData.value.hours))
|
||||
|
||||
watch(totalPrice, () => {
|
||||
emit('totalAmount', totalPrice.value)
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<div class="add-products-header mb-2 d-none d-md-flex mb-4">
|
||||
<VRow class="me-10">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<h6 class="text-h6">
|
||||
Item
|
||||
</h6>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
>
|
||||
<h6 class="text-h6 ps-2">
|
||||
Cost
|
||||
</h6>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
>
|
||||
<h6 class="text-h6 ps-2">
|
||||
Hours
|
||||
</h6>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
>
|
||||
<h6 class="text-h6">
|
||||
Price
|
||||
</h6>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<VCard
|
||||
flat
|
||||
border
|
||||
class="d-flex flex-sm-row flex-column-reverse"
|
||||
>
|
||||
<!-- 👉 Left Form -->
|
||||
<div class="pa-5 flex-grow-1">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="selectedItem"
|
||||
:items="itemsOptions"
|
||||
item-title="title"
|
||||
item-value="title"
|
||||
label="Select Item"
|
||||
placeholder="Select Item"
|
||||
class="mb-5"
|
||||
/>
|
||||
|
||||
<VTextarea
|
||||
v-model="localProductData.description"
|
||||
rows="2"
|
||||
label="Description"
|
||||
placeholder="Item description"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
sm="4"
|
||||
>
|
||||
<VTextField
|
||||
v-model="localProductData.cost"
|
||||
type="number"
|
||||
label="Cost"
|
||||
placeholder="100"
|
||||
/>
|
||||
|
||||
<div class="text-high-emphasis mt-4">
|
||||
<p class="mb-1">
|
||||
Discount
|
||||
</p>
|
||||
<span>0%</span>
|
||||
<span class="mx-2">
|
||||
0%
|
||||
<VTooltip activator="parent">Tax 1</VTooltip>
|
||||
</span>
|
||||
<span>
|
||||
0%
|
||||
<VTooltip activator="parent">Tax 2</VTooltip>
|
||||
</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
sm="4"
|
||||
>
|
||||
<VTextField
|
||||
v-model="localProductData.hours"
|
||||
type="number"
|
||||
label="Hours"
|
||||
placeholder="5"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="2"
|
||||
sm="4"
|
||||
>
|
||||
<p class="my-2">
|
||||
<span class="d-inline d-md-none">Price: </span>
|
||||
<span class="text-high-emphasis">${{ totalPrice }}</span>
|
||||
</p>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Item Actions -->
|
||||
<div
|
||||
class="d-flex flex-column align-end item-actions"
|
||||
:class="$vuetify.display.smAndUp ? 'border-s' : 'border-b' "
|
||||
>
|
||||
<IconBtn @click="removeProduct">
|
||||
<VIcon
|
||||
:size="20"
|
||||
icon="ri-close-line"
|
||||
/>
|
||||
</IconBtn>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
133
resources/js/views/apps/invoice/InvoiceSendInvoiceDrawer.vue
Normal file
133
resources/js/views/apps/invoice/InvoiceSendInvoiceDrawer.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
|
||||
const props = defineProps({
|
||||
isDrawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:isDrawerOpen',
|
||||
'submit',
|
||||
])
|
||||
|
||||
const emailFrom = ref('shelbyComapny@email.com')
|
||||
const emailTo = ref('qConsolidated@email.com')
|
||||
const invoiceSubject = ref('Invoice of purchased Admin Templates')
|
||||
|
||||
const paymentMessage = ref(`Dear Queen Consolidated,
|
||||
|
||||
Thank you for your business, always a pleasure to work with you!
|
||||
|
||||
We have generated a new invoice in the amount of $95.59
|
||||
|
||||
We would appreciate payment of this invoice by 05/11/2019`)
|
||||
|
||||
const onSubmit = () => {
|
||||
emit('update:isDrawerOpen', false)
|
||||
emit('submit', {
|
||||
emailFrom: emailFrom.value,
|
||||
emailTo: emailTo.value,
|
||||
invoiceSubject: invoiceSubject.value,
|
||||
paymentMessage: paymentMessage.value,
|
||||
})
|
||||
}
|
||||
|
||||
const handleDrawerModelValueUpdate = val => {
|
||||
emit('update:isDrawerOpen', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VNavigationDrawer
|
||||
temporary
|
||||
location="end"
|
||||
:width="400"
|
||||
:model-value="props.isDrawerOpen"
|
||||
class="scrollable-content"
|
||||
@update:model-value="handleDrawerModelValueUpdate"
|
||||
>
|
||||
<!-- 👉 Header -->
|
||||
<AppDrawerHeaderSection
|
||||
title="Send Invoice"
|
||||
@cancel="$emit('update:isDrawerOpen', false)"
|
||||
/>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<PerfectScrollbar :options="{ wheelPropagation: false }">
|
||||
<VCard flat>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="onSubmit">
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="emailFrom"
|
||||
label="From"
|
||||
placeholder="sender@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="emailTo"
|
||||
label="To"
|
||||
placeholder="receiver@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="invoiceSubject"
|
||||
label="Subject"
|
||||
placeholder="Invoice of purchased Admin Templates"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="paymentMessage"
|
||||
rows="10"
|
||||
label="Message"
|
||||
placeholder="Thank you for your business, always a pleasure to work with you!"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<div class="mb-6">
|
||||
<VChip
|
||||
label
|
||||
color="primary"
|
||||
size="small"
|
||||
>
|
||||
<VIcon
|
||||
start
|
||||
icon="ri-links-line"
|
||||
/>
|
||||
Invoice Attached
|
||||
</VChip>
|
||||
</div>
|
||||
<VBtn
|
||||
type="submit"
|
||||
class="me-3"
|
||||
>
|
||||
Send
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
@click="$emit('update:isDrawerOpen', false)"
|
||||
>
|
||||
Cancel
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</PerfectScrollbar>
|
||||
</VNavigationDrawer>
|
||||
</template>
|
1
resources/js/views/apps/invoice/types.js
Normal file
1
resources/js/views/apps/invoice/types.js
Normal file
@@ -0,0 +1 @@
|
||||
export {}
|
Reference in New Issue
Block a user