ADD: Belonging Transactions To Menu
FIX: Upload Document and Image system completely FIX: ImageGallery and ImageGallery components bugs FORCE: Dark Mode FIX: Minor bug in login page
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="app-dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|||||||
@@ -12,26 +12,30 @@ function formatCurrency(value) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
BelongingService.getAllBelongings().then((data) => (products.value = data));
|
BelongingService.getAllBelongings().then((data) => (products.value = data));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function editBelonging(belongingId) {
|
||||||
|
router.push('/belonging/edit-belonging/' + belongingId);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="font-semibold text-xl mb-4">آخرین دارایی های افزوده شده</div>
|
<div class="font-semibold text-xl mb-4">آخرین دارایی های افزوده شده</div>
|
||||||
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
|
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
|
||||||
<Column style="width: 15%" header="Image">
|
<Column style="min-width: 15%" header="Image">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<img :src="`${slotProps.data.mainImageUrl}`" :alt="slotProps.data.name" width="50" class="shadow" />
|
<img :src="`${slotProps.data.mainImageUrl}`" :alt="slotProps.data.name" width="50" class="shadow" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="name" header="نام" :sortable="true" style="width: 35%"></Column>
|
<Column field="name" header="نام" :sortable="true" style="min-width: 35%"></Column>
|
||||||
<Column field="price" header="دسته بندی" :sortable="true" style="width: 35%">
|
<Column field="price" header="دسته بندی" :sortable="true" style="min-width: 30%">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
{{ slotProps.data.category.nativeName }}
|
{{ slotProps.data.category?.nativeName }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column style="width: 15%" header="نمایش">
|
<Column style="min-width: 15%" header="نمایش">
|
||||||
<template #body>
|
<template #body>
|
||||||
<Button icon="pi pi-search" type="button" class="p-button-text"></Button>
|
<Button icon="pi pi-search" type="button" class="p-button-text" @click="editBelonging(slotProps.data._id)"></Button>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ const model = ref([
|
|||||||
label: 'مدیریت اموال',
|
label: 'مدیریت اموال',
|
||||||
items: [
|
items: [
|
||||||
{ label: 'ایجاد اموال', icon: 'pi pi-fw pi-file-plus', to: '/belonging/create-belonging' },
|
{ label: 'ایجاد اموال', icon: 'pi pi-fw pi-file-plus', to: '/belonging/create-belonging' },
|
||||||
{ label: 'لیست اموال', icon: 'pi pi-fw pi-file', to: '/belonging/list-belongings' }
|
{ label: 'لیست اموال', icon: 'pi pi-fw pi-file', to: '/belonging/list-belongings' },
|
||||||
|
{ label: 'لیست تراکنش اموال', icon: 'pi pi-fw pi-plus', to: '/transactions/belonging-transactions' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import PrimeVue from 'primevue/config';
|
|||||||
import ConfirmationService from 'primevue/confirmationservice';
|
import ConfirmationService from 'primevue/confirmationservice';
|
||||||
import ToastService from 'primevue/toastservice';
|
import ToastService from 'primevue/toastservice';
|
||||||
import * as VueCookies from 'vue-cookies';
|
import * as VueCookies from 'vue-cookies';
|
||||||
|
import Tooltip from 'primevue/tooltip';
|
||||||
|
|
||||||
import '@/assets/styles.scss';
|
import '@/assets/styles.scss';
|
||||||
import '@/assets/tailwind.css';
|
import '@/assets/tailwind.css';
|
||||||
@@ -32,5 +33,6 @@ app.use(ConfirmationService);
|
|||||||
app.use(VueCookies, { expires: '1000d', sameSite: 'None', secure: true });
|
app.use(VueCookies, { expires: '1000d', sameSite: 'None', secure: true });
|
||||||
|
|
||||||
app.component('font-awesome-icon', FontAwesomeIcon);
|
app.component('font-awesome-icon', FontAwesomeIcon);
|
||||||
|
app.directive('tooltip', Tooltip);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const TransactionService = {
|
||||||
|
/**
|
||||||
|
* Get a single transaction by ID
|
||||||
|
* @param {string} transactionId - The transaction ID
|
||||||
|
* @returns {Promise<Object>} - Transaction data
|
||||||
|
*/
|
||||||
|
getTransaction: async (transactionId) => {
|
||||||
|
const config = {
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/transactions/admin/get-one/' + transactionId,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all transactions
|
||||||
|
* @returns {Promise<Array>} - List of all transactions
|
||||||
|
*/
|
||||||
|
getAllTransactions: async () => {
|
||||||
|
const config = {
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/transactions/admin/get-all',
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transactions related to a specific belonging
|
||||||
|
* @param {string} belongingId - The belonging ID
|
||||||
|
* @returns {Promise<Array>} - List of transactions for the belonging
|
||||||
|
*/
|
||||||
|
getTransactionsByBelongingId: async (belongingId) => {
|
||||||
|
const config = {
|
||||||
|
method: 'get',
|
||||||
|
url: `/api/transactions/admin/get-all-by-belonging/${belongingId}`,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
console.log(response.data);
|
||||||
|
return response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stock of a belonging ID
|
||||||
|
* @param {string} belongingId - The belonging ID
|
||||||
|
* @returns {Promise<Array>} - List of transactions for the belonging
|
||||||
|
*/
|
||||||
|
getStockByBelongingId: async (belongingId) => {
|
||||||
|
const config = {
|
||||||
|
method: 'get',
|
||||||
|
url: `/api/transactions/admin/get-stock-by-belonging/${belongingId}`,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
console.log(response.data);
|
||||||
|
return response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new transaction
|
||||||
|
* @param {Object} data - Transaction data
|
||||||
|
* @returns {Promise<string>} - Success message or error
|
||||||
|
*/
|
||||||
|
createTransaction: async (data) => {
|
||||||
|
const config = {
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/transactions/admin/create',
|
||||||
|
withCredentials: true,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return 'success';
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return err.response?.data?.message || 'Error creating transaction';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing transaction
|
||||||
|
* @param {string} transactionId - The transaction ID
|
||||||
|
* @param {Object} data - Updated transaction data
|
||||||
|
* @returns {Promise<string>} - Success message or error
|
||||||
|
*/
|
||||||
|
updateTransaction: async (transactionId, data) => {
|
||||||
|
const config = {
|
||||||
|
method: 'put',
|
||||||
|
url: '/api/transactions/admin/update/' + transactionId,
|
||||||
|
withCredentials: true,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return 'success';
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return err.response?.data?.message || 'Error updating transaction';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a transaction
|
||||||
|
* @param {string} transactionId - The transaction ID
|
||||||
|
* @returns {Promise<string>} - Success message or error
|
||||||
|
*/
|
||||||
|
deleteTransaction: async (transactionId) => {
|
||||||
|
const config = {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/api/transactions/admin/delete/' + transactionId,
|
||||||
|
withCredentials: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return 'success';
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return err.response?.data?.message || 'Error deleting transaction';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,18 +9,31 @@ import { useToast } from 'primevue/usetoast';
|
|||||||
import { BelongingService } from '@/service/BelongingService';
|
import { BelongingService } from '@/service/BelongingService';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import MapComponent from '@/views/uikit/MapComponent.vue';
|
import MapComponent from '@/views/uikit/MapComponent.vue';
|
||||||
|
import TransactionDialog from './TransactionDialog.vue';
|
||||||
|
import { TransactionService } from '@/service/TransactionService';
|
||||||
|
import { FilterMatchMode, FilterOperator } from '@primevue/core/api';
|
||||||
|
import Popover from 'primevue/popover';
|
||||||
|
|
||||||
|
// For the single popover approach
|
||||||
|
const op = ref();
|
||||||
|
const activeDescription = ref('');
|
||||||
|
|
||||||
|
const toggle = (event, data) => {
|
||||||
|
activeDescription.value = data;
|
||||||
|
op.value.toggle(event);
|
||||||
|
};
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const categories = ref([]);
|
const categories = ref([]);
|
||||||
const selectedCategory = ref();
|
const selectedCategory = ref();
|
||||||
const belongingName = ref('');
|
|
||||||
const description = ref('');
|
|
||||||
const imageNames = ref([]);
|
|
||||||
const features = ref([]);
|
const features = ref([]);
|
||||||
const featuresValues = ref({});
|
const featuresValues = ref({});
|
||||||
const documentNames = ref([]);
|
|
||||||
const documentData = ref([]);
|
const documentData = ref([]);
|
||||||
const mainImageName = ref([]);
|
const mainImageUrlsWithData = ref([]);
|
||||||
|
const uploadedMainImageUrls = ref([]);
|
||||||
|
const documentUrlsWithData = ref([]);
|
||||||
|
const uploadedDocumentUrls = ref([]);
|
||||||
|
const editMode = ref(false);
|
||||||
const belonging = ref({
|
const belonging = ref({
|
||||||
location: {
|
location: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
@@ -36,13 +49,102 @@ const mapKey = 'web.79abbab4970e401781f434da3e41cfa6';
|
|||||||
const serviceKey = 'service.11d860f52c434bd29e5f84d0a0a41108';
|
const serviceKey = 'service.11d860f52c434bd29e5f84d0a0a41108';
|
||||||
const mapRef = ref();
|
const mapRef = ref();
|
||||||
|
|
||||||
|
const transactions = ref([]);
|
||||||
|
const stockData = ref({});
|
||||||
|
|
||||||
|
const handleDocumentUpload = (newDocumentUrls) => {
|
||||||
|
uploadedDocumentUrls.value = newDocumentUrls.value;
|
||||||
|
const tempConcatImages = [...belonging.value?.documentUrls, ...newDocumentUrls.value];
|
||||||
|
documentUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDocumentDelete = (newDocumentUrls) => {
|
||||||
|
uploadedDocumentUrls.value = newDocumentUrls;
|
||||||
|
const tempConcatImages = [...belonging.value?.documentUrls, ...newDocumentUrls.value];
|
||||||
|
documentUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDocumentGalleryDelete = (deletedDocumentUrl, index) => {
|
||||||
|
console.log('deletedImageUrl', deletedDocumentUrl, 'at index', index);
|
||||||
|
|
||||||
|
// Find which array contains the image to delete
|
||||||
|
if (index < belonging.value.documentUrls.length) {
|
||||||
|
// Remove only at specific index from belonging.value.mainImageNames
|
||||||
|
belonging.value.documentUrls.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// Adjust index for uploadedMainImageNames array
|
||||||
|
const adjustedIndex = index - belonging.value?.documentUrls.length;
|
||||||
|
uploadedDocumentUrls.value.splice(adjustedIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recombine arrays
|
||||||
|
const tempConcatImages = [...belonging.value?.documentUrls, ...uploadedDocumentUrls.value];
|
||||||
|
documentUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageUpload = (newImageUrls) => {
|
||||||
|
uploadedMainImageUrls.value = newImageUrls.value;
|
||||||
|
const tempConcatImages = [...belonging.value?.mainImageUrls, ...newImageUrls.value];
|
||||||
|
mainImageUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageDelete = (newImageUrls) => {
|
||||||
|
uploadedMainImageUrls.value = newImageUrls;
|
||||||
|
const tempConcatImages = [...belonging.value?.mainImageUrls.value, ...newImageUrls.value];
|
||||||
|
mainImageUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageGalleryDelete = (deletedImageUrl, index) => {
|
||||||
|
console.log('deletedImageUrl', deletedImageUrl, 'at index', index);
|
||||||
|
|
||||||
|
// Find which array contains the image to delete
|
||||||
|
if (index < belonging.value?.mainImageUrls.length) {
|
||||||
|
// Remove only at specific index from belonging.value.mainImageNames
|
||||||
|
belonging.value?.mainImageUrls.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// Adjust index for uploadedMainImageNames array
|
||||||
|
const adjustedIndex = index - belonging.value?.mainImageUrls.length;
|
||||||
|
uploadedMainImageUrls.value?.splice(adjustedIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recombine arrays
|
||||||
|
const tempConcatImages = [...belonging.value?.mainImageUrls, ...uploadedMainImageUrls.value];
|
||||||
|
mainImageUrlsWithData.value = fillImagesArray(tempConcatImages);
|
||||||
|
};
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
categories.value = await CategoryService.getAllCategories();
|
categories.value = await CategoryService.getAllCategories();
|
||||||
console.log(route.name);
|
console.log(route.name);
|
||||||
if (route.name === 'edit-belonging') {
|
if (route.name === 'edit-belonging') {
|
||||||
|
editMode.value = true;
|
||||||
belonging.value = await BelongingService.getBelonging(route.params.belongingId);
|
belonging.value = await BelongingService.getBelonging(route.params.belongingId);
|
||||||
|
transactions.value = await TransactionService.getTransactionsByBelongingId(route.params.belongingId);
|
||||||
|
stockData.value = await TransactionService.getStockByBelongingId(route.params.belongingId);
|
||||||
|
mainImageUrlsWithData.value = fillImagesArray(belonging.value?.mainImageUrls || []);
|
||||||
|
documentUrlsWithData.value = fillImagesArray(belonging.value?.documentUrls || []);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const showTransactionDialog = ref(false);
|
||||||
|
|
||||||
|
const fillImagesArray = (imageUrls) => {
|
||||||
|
const images = [];
|
||||||
|
for (let url of imageUrls) {
|
||||||
|
images.push({ url: url });
|
||||||
|
}
|
||||||
|
return images;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTransactionDialog = () => {
|
||||||
|
showTransactionDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTransactionSaved = async () => {
|
||||||
|
if (editMode.value && belonging.value?._id) {
|
||||||
|
belonging.value = await BelongingService.getBelonging(belonging.value._id);
|
||||||
|
}
|
||||||
|
toast.add({ severity: 'success', summary: 'موفق', detail: 'تراکنش با موفقیت ثبت شد', life: 3000 });
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectCategory = ($event) => {
|
const handleSelectCategory = ($event) => {
|
||||||
if ($event.value) {
|
if ($event.value) {
|
||||||
belonging.value.category = $event.value;
|
belonging.value.category = $event.value;
|
||||||
@@ -63,6 +165,8 @@ const saveBelonging = async () => {
|
|||||||
let status = 'failed';
|
let status = 'failed';
|
||||||
console.log(belonging);
|
console.log(belonging);
|
||||||
if (belonging?.value._id?.trim()) {
|
if (belonging?.value._id?.trim()) {
|
||||||
|
belonging.value.mainImageUrls = mainImageUrlsWithData.value.map((x) => x.url);
|
||||||
|
belonging.value.documentUrls = documentUrlsWithData.value.map((x) => x.url);
|
||||||
status = await BelongingService.updateBelonging(belonging.value._id, belonging.value);
|
status = await BelongingService.updateBelonging(belonging.value._id, belonging.value);
|
||||||
} else {
|
} else {
|
||||||
status = await BelongingService.createBelonging(belonging.value);
|
status = await BelongingService.createBelonging(belonging.value);
|
||||||
@@ -93,63 +197,255 @@ watch(
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filters = ref({
|
||||||
|
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
|
type: { value: null, matchMode: FilterMatchMode.EQUALS },
|
||||||
|
status: { value: null, matchMode: FilterMatchMode.EQUALS },
|
||||||
|
value: { value: null, matchMode: FilterMatchMode.BETWEEN },
|
||||||
|
date: { value: null, matchMode: FilterMatchMode.DATE_IS }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Options for filters
|
||||||
|
const typeOptions = [
|
||||||
|
{ label: 'واریز', value: 'deposit' },
|
||||||
|
{ label: 'برداشت', value: 'withdraw' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: 'ناموفق', value: 'failed' },
|
||||||
|
{ label: 'در انتظار', value: 'pending' },
|
||||||
|
{ label: 'تکمیل شده', value: 'finished' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper functions for translations and formatting
|
||||||
|
const getTypeTranslation = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'deposit':
|
||||||
|
return 'واریز';
|
||||||
|
case 'withdraw':
|
||||||
|
return 'برداشت';
|
||||||
|
default:
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeSeverity = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'deposit':
|
||||||
|
return 'success';
|
||||||
|
case 'withdraw':
|
||||||
|
return 'danger';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusTranslation = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'failed':
|
||||||
|
return 'ناموفق';
|
||||||
|
case 'pending':
|
||||||
|
return 'در انتظار';
|
||||||
|
case 'finished':
|
||||||
|
return 'تکمیل شده';
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusSeverity = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'failed':
|
||||||
|
return 'danger';
|
||||||
|
case 'pending':
|
||||||
|
return 'warning';
|
||||||
|
case 'finished':
|
||||||
|
return 'success';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCurrency = (value) => {
|
||||||
|
return new Intl.NumberFormat('fa-IR', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IRR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(Math.abs(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return new Intl.DateTimeFormat('fa-IR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<TransactionDialog v-model:visible="showTransactionDialog" :belongingId="belonging._id" @saved="onTransactionSaved" />
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div class="font-semibold text-xl mb-4">ایجاد اموال</div>
|
<div class="mb-4" v-if="editMode === true">
|
||||||
<p>از طریق این صفحه می توانید اموال جدید را به سوابق اضافه کنید.</p>
|
<Button @click="openTransactionDialog">افزایش یا کاهش موجودی</Button>
|
||||||
|
</div>
|
||||||
|
<div class="font-semibold text-xl mb-4" v-if="editMode === false">ایجاد اموال</div>
|
||||||
|
<p v-if="editMode === false">از طریق این صفحه می توانید اموال جدید را به سوابق اضافه کنید.</p>
|
||||||
|
<div class="font-semibold text-xl mb-4" v-if="editMode === true">
|
||||||
|
در حال ویرایش
|
||||||
|
<span class="text-primary">{{ belonging.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex my-6">
|
<div class="flex my-6">
|
||||||
<div class="w-3/4 ml-3">
|
<div class="w-3/4 ml-3">
|
||||||
<div class="">
|
<div class="">
|
||||||
<label> نام دارایی </label>
|
<label class="text-lg"> نام دارایی </label>
|
||||||
<InputText v-model="belonging.name" class="w-full my-2" placeholder="مثلا: 100 عدد کولر گازی" />
|
<InputText v-model="belonging.name" class="w-full my-2" placeholder="مثلا: 100 عدد کولر گازی" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<label> توضیحات </label>
|
<label class="text-lg"> توضیحات </label>
|
||||||
<Textarea v-model="belonging.description" class="w-full my-2" placeholder="مثلا: در تاریخ 1403/09/11 خریداری شد." rows="6" />
|
<Textarea v-model="belonging.description" class="w-full my-2" placeholder="مثلا: در تاریخ 1403/09/11 خریداری شد." rows="6" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<label> تصاویر، فایل ها و مدارک </label>
|
<label class="text-lg"> تصاویر، فایل ها و مدارک </label>
|
||||||
<ImageUploader v-model="belonging.documentNames" :max-files="50" mode="private" delete-endpoint="/api/files/bucket/delete/belonging-documents" upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-documents" />
|
<ImageUploader
|
||||||
<div v-if="signedImageUrls.length > 0">
|
v-model="uploadedDocumentUrls"
|
||||||
<ImageGallery v-model="documentData" gallery-id="images" />
|
:max-files="50"
|
||||||
|
mode="private"
|
||||||
|
delete-endpoint="/api/files/bucket/delete-file/belonging-documents"
|
||||||
|
upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-documents"
|
||||||
|
@upload="handleDocumentUpload"
|
||||||
|
@delete="handleDocumentDelete"
|
||||||
|
/>
|
||||||
|
<div v-if="documentUrlsWithData.length > 0">
|
||||||
|
<ImageGallery v-model="documentUrlsWithData" gallery-id="documents" @delete-image="handleDocumentGalleryDelete" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6" v-if="signedDocumentsUrls.length > 0">
|
<div class="my-6" v-if="signedDocumentsUrls.length > 0">
|
||||||
<label> فایل ها و مدارک </label>
|
<label class="text-lg"> فایل ها و مدارک </label>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6 max-h-96 overflow-y-auto" v-if="features.length > 0">
|
<div class="my-6 max-h-96 overflow-y-auto" v-if="features.length > 0">
|
||||||
<label> اطلاعات دارایی </label>
|
<label class="text-lg"> اطلاعات دارایی </label>
|
||||||
<div class="my-6 mr-10" v-for="feature of features">
|
<div class="my-6 mr-10" v-for="feature of features">
|
||||||
<label>{{ feature.nativeName }}</label>
|
<label>{{ feature.nativeName }}</label>
|
||||||
<InputText v-model="featuresValues[feature._id]" class="w-1/3 my-2 block" placeholder="" />
|
<InputText v-model="featuresValues[feature._id]" class="w-1/3 my-2 block" placeholder="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-10">
|
<div class="my-10">
|
||||||
<label> موقعیت دارایی </label>
|
<label class="text-lg"> موقعیت دارایی </label>
|
||||||
<MapComponent v-model="belonging.location.coordinates" />
|
<MapComponent v-model="belonging.location.coordinates" />
|
||||||
<!-- <MapIrComponent />-->
|
<!-- <MapIrComponent />-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="my-10" v-if="editMode">
|
||||||
|
<label class="text-lg"> موجودی </label>
|
||||||
|
<p class="mx-3 text-blue-400">موجودی فعلی : {{ stockData.finishedInventory }}</p>
|
||||||
|
<p class="mx-3 text-green-400">موجودی در حال اضافه شدن : {{ stockData.pendingDepositsInventory }}</p>
|
||||||
|
<p class="mx-3 text-red-400">
|
||||||
|
موجودی در حال کم شدن : <span dir="ltr">{{ stockData.pendingWithdrawsInventory }}</span>
|
||||||
|
</p>
|
||||||
|
<div class="mx-6 py-4">
|
||||||
|
<label> تراکنش ها </label>
|
||||||
|
<div class="card">
|
||||||
|
<DataTable
|
||||||
|
:value="transactions"
|
||||||
|
paginator
|
||||||
|
:rows="20"
|
||||||
|
:rowsPerPageOptions="[20, 50, 100]"
|
||||||
|
tableStyle="min-width: 50rem"
|
||||||
|
stripedRows
|
||||||
|
v-model:filters="filters"
|
||||||
|
filterDisplay="menu"
|
||||||
|
:globalFilterFields="['type', 'status', 'value', 'date', 'description']"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex justify-content-between">
|
||||||
|
<h3 class="m-0">تراکنشها</h3>
|
||||||
|
<span class="p-input-icon-left">
|
||||||
|
<i class="pi pi-search" />
|
||||||
|
<InputText v-model="filters['global'].value" placeholder="جستجو..." />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Column field="id" header="شناسه" sortable style="width: 25%"></Column>
|
||||||
|
|
||||||
|
<Column field="type" header="نوع" sortable style="width: 15%">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :severity="getTypeSeverity(data.type)" :value="getTypeTranslation(data.type)" />
|
||||||
|
</template>
|
||||||
|
<template #filter="{ filterModel, filterCallback }">
|
||||||
|
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="typeOptions" optionLabel="label" optionValue="value" placeholder="انتخاب نوع" class="p-column-filter" showClear />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="status" header="وضعیت" sortable style="width: 15%">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :severity="getStatusSeverity(data.status)" :value="getStatusTranslation(data.status)" />
|
||||||
|
</template>
|
||||||
|
<template #filter="{ filterModel, filterCallback }">
|
||||||
|
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="statusOptions" optionLabel="label" optionValue="value" placeholder="انتخاب وضعیت" class="p-column-filter" showClear />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="value" header="تعداد" sortable style="width: 15%">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<span :class="data.value >= 0 ? 'text-green-500' : 'text-red-500'" dir="ltr"> {{ data.value }} </span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="date" header="تاریخ" sortable style="width: 20%">
|
||||||
|
<template #body="{ data }">
|
||||||
|
{{ formatDate(data.date) }}
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column field="description" header="توضیحات" style="width: 10%">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Button v-if="data.description" icon="pi pi-info-circle" text rounded aria-label="توضیحات" @click="toggle($event, data.description)" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Popover ref="op" :showCloseIcon="true" :autoHide="false">
|
||||||
|
<div class="p-2">{{ activeDescription }}</div>
|
||||||
|
</Popover>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4">
|
<div class="w-1/4">
|
||||||
<div>
|
<div>
|
||||||
<label> دسته بندی </label>
|
<label class="text-lg"> دسته بندی </label>
|
||||||
<Listbox @change="handleSelectCategory($event)" :options="categories" optionLabel="nativeName" optionValue="_id" checkmark :highlightOnSelect="false" class="w-full my-2" />
|
<Listbox @change="handleSelectCategory($event)" v-model="belonging.category" :options="categories" optionLabel="nativeName" optionValue="_id" checkmark :highlightOnSelect="false" class="w-full my-2" />
|
||||||
</div>
|
</div>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<label> تصویر اصلی </label>
|
<label class="text-lg"> تصویر اصلی </label>
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
v-model="belonging.mainImageName"
|
v-model="uploadedMainImageUrls"
|
||||||
accepted-files="image/*"
|
accepted-files="image/*"
|
||||||
:max-files="1"
|
:max-files="1"
|
||||||
mode="private"
|
mode="private"
|
||||||
delete-endpoint="/api/files/bucket/delete/belonging-images"
|
delete-endpoint="/api/files/bucket/delete/belonging-images"
|
||||||
upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-images"
|
upload-endpoint="/api/files/bucket/upload-multi-filepond/belonging-images"
|
||||||
|
@upload="handleImageUpload"
|
||||||
|
@delete="handleImageDelete"
|
||||||
/>
|
/>
|
||||||
|
<div class="my-10 border rounded-lg p-3 border-surface-700" v-if="mainImageUrlsWithData.length > 0">
|
||||||
|
<label for="ImageGallery">گالری تصاویر</label>
|
||||||
|
<div class="">
|
||||||
|
<ImageGallery v-model="mainImageUrlsWithData" gallery-id="productImages" @delete-image="handleImageGalleryDelete" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-8">
|
<div class="my-8">
|
||||||
<label> برچسب ها </label>
|
<label class="text-lg"> واحد </label>
|
||||||
|
<InputText v-model="belonging.unit" class="w-full" placeholder="مثلا: عدد، متر، و..." />
|
||||||
|
</div>
|
||||||
|
<div class="my-8">
|
||||||
|
<label class="text-lg"> برچسب ها </label>
|
||||||
<KiwiChips v-model="belonging.tags" class="w-full" placeholder="مثلا: زمین، ملک، کیانپارس..." />
|
<KiwiChips v-model="belonging.tags" class="w-full" placeholder="مثلا: زمین، ملک، کیانپارس..." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,3 +459,9 @@ watch(
|
|||||||
<style>
|
<style>
|
||||||
@import url('leaflet/dist/leaflet.css');
|
@import url('leaflet/dist/leaflet.css');
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
label {
|
||||||
|
@apply text-primary;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -34,28 +34,6 @@ function hideDialog() {
|
|||||||
submitted.value = false;
|
submitted.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveProduct() {
|
|
||||||
submitted.value = true;
|
|
||||||
|
|
||||||
if (product?.value.name?.trim()) {
|
|
||||||
if (product.value.id) {
|
|
||||||
product.value.inventoryStatus = product.value.inventoryStatus.value ? product.value.inventoryStatus.value : product.value.inventoryStatus;
|
|
||||||
products.value[findIndexById(product.value.id)] = product.value;
|
|
||||||
toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Updated', life: 3000 });
|
|
||||||
} else {
|
|
||||||
product.value.id = createId();
|
|
||||||
product.value.code = createId();
|
|
||||||
product.value.image = 'product-placeholder.svg';
|
|
||||||
product.value.inventoryStatus = product.value.inventoryStatus ? product.value.inventoryStatus.value : 'INSTOCK';
|
|
||||||
products.value.push(product.value);
|
|
||||||
toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Created', life: 3000 });
|
|
||||||
}
|
|
||||||
|
|
||||||
productDialog.value = false;
|
|
||||||
product.value = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
function editBelonging(belongingId) {
|
function editBelonging(belongingId) {
|
||||||
@@ -166,14 +144,14 @@ function getStatusLabel(status) {
|
|||||||
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
||||||
<Column header="تصویر">
|
<Column header="تصویر">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<img :src="`${slotProps.data.mainImageUrl}`" :alt="slotProps.data.name" class="rounded" style="width: 64px" />
|
<img :src="`${slotProps.data.mainImageUrls[0]}`" :alt="slotProps.data.name" class="rounded" style="max-width: 64px" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="belongingCode" header="کد" style="min-width: 12rem"></Column>
|
<Column field="belongingCode" header="کد" style="min-width: 4rem"></Column>
|
||||||
<Column field="name" header="نام" sortable style="min-width: 16rem"></Column>
|
<Column field="name" header="نام" sortable style="min-width: 16rem"></Column>
|
||||||
<Column field="category" header="دسته بندی" sortable style="min-width: 10rem">
|
<Column field="category" header="دسته بندی" sortable style="min-width: 6rem">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
{{ slotProps.data.category.nativeName }}
|
{{ slotProps.data.category?.nativeName }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column :exportable="false" style="min-width: 12rem">
|
<Column :exportable="false" style="min-width: 12rem">
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { TransactionService } from '@/service/TransactionService';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
belongingId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'saved']);
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const transaction = reactive({
|
||||||
|
type: 'deposit',
|
||||||
|
status: 'pending',
|
||||||
|
value: null,
|
||||||
|
description: '',
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
belongingId: props.belongingId
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeOptions = [
|
||||||
|
{ label: 'واریز', value: 'deposit' },
|
||||||
|
{ label: 'برداشت', value: 'withdraw' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: 'در انتظار', value: 'pending' },
|
||||||
|
{ label: 'تکمیل شده', value: 'finished' },
|
||||||
|
{ label: 'ناموفق', value: 'failed' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const errors = reactive({
|
||||||
|
type: '',
|
||||||
|
status: '',
|
||||||
|
value: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
if (!transaction.type) {
|
||||||
|
errors.type = 'نوع تراکنش الزامی است';
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
errors.type = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transaction.status) {
|
||||||
|
errors.status = 'وضعیت تراکنش الزامی است';
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
errors.status = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transaction.value || transaction.value <= 0) {
|
||||||
|
errors.value = 'مقدار باید بزرگتر از صفر باشد';
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
errors.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveTransaction = async () => {
|
||||||
|
if (!validateForm()) {
|
||||||
|
toast.add({ severity: 'error', summary: 'خطا', detail: 'لطفا تمام فیلدهای الزامی را پر کنید', life: 3000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await TransactionService.createTransaction(transaction);
|
||||||
|
if (result === 'success') {
|
||||||
|
toast.add({ severity: 'success', summary: 'موفق', detail: 'تراکنش با موفقیت ثبت شد', life: 3000 });
|
||||||
|
emit('saved');
|
||||||
|
closeDialog();
|
||||||
|
} else {
|
||||||
|
toast.add({ severity: 'error', summary: 'خطا', detail: result || 'خطا در ثبت تراکنش', life: 3000 });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در ارتباط با سرور', life: 3000 });
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog v-model:visible="props.visible" :modal="true" :closable="true" :style="{ width: '450px' }" header="افزایش یا کاهش موجودی" @update:visible="closeDialog">
|
||||||
|
<div class="p-fluid">
|
||||||
|
<div class="field mb-4">
|
||||||
|
<label class="font-bold mb-2 block">نوع تراکنش</label>
|
||||||
|
<SelectButton v-model="transaction.type" :options="typeOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||||
|
<small v-if="errors.type" class="p-error">{{ errors.type }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field mb-4">
|
||||||
|
<label class="font-bold mb-2 block">وضعیت</label>
|
||||||
|
<SelectButton v-model="transaction.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||||
|
<small v-if="errors.status" class="p-error">{{ errors.status }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field mb-4">
|
||||||
|
<label class="font-bold mb-2 block">مقدار</label>
|
||||||
|
<InputNumber v-model="transaction.value" placeholder="مقدار را وارد کنید" class="w-full" :min="0" />
|
||||||
|
<small v-if="errors.value" class="p-error">{{ errors.value }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field mb-4">
|
||||||
|
<label class="font-bold mb-2 block">توضیحات</label>
|
||||||
|
<Textarea v-model="transaction.description" placeholder="توضیحات تراکنش را وارد کنید" rows="3" class="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button label="انصراف" icon="pi pi-times" class="p-button-text" @click="closeDialog" />
|
||||||
|
<Button label="ذخیره" icon="pi pi-check" @click="saveTransaction" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
@@ -3,43 +3,43 @@ import FloatingConfigurator from '@/components/FloatingConfigurator.vue';
|
|||||||
import { onBeforeMount, ref } from 'vue';
|
import { onBeforeMount, ref } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const checked = ref(false);
|
const checked = ref(false);
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false);
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true
|
loginLoading.value = true;
|
||||||
const response = await axios.post('/api/users/user/login-email', {
|
const response = await axios.post('/api/users/login-email', {
|
||||||
email: email.value,
|
email: email.value,
|
||||||
password: password.value,
|
password: password.value
|
||||||
})
|
});
|
||||||
console.log(response)
|
console.log(response);
|
||||||
Cookies.set('bm-token', response.data.data.token)
|
Cookies.set('bm-token', response.data.data.token);
|
||||||
localStorage.setItem('bm-token', response.data.data.token)
|
localStorage.setItem('bm-token', response.data.data.token);
|
||||||
await router.push('/')
|
await router.push('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('err', error)
|
console.log('err', error);
|
||||||
loginLoading.value = false
|
loginLoading.value = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const token = Cookies.get('bm-token')
|
const token = Cookies.get('bm-token');
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/users/user/get-self', {
|
const response = await axios.get('/api/users/user/get-self', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
console.log(response)
|
console.log(response);
|
||||||
await router.push('/')
|
await router.push('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :id="galleryId" class="pswp-gallery pswp-gallery--single-column my-3 flex min-w-0 flex-row flex-nowrap overflow-x-auto rounded-md bg-brand-dark-3 p-3">
|
<div :id="galleryId" class="pswp-gallery pswp-gallery--single-column my-3 flex min-w-0 flex-row flex-nowrap overflow-x-auto rounded-md bg-brand-dark-3 p-3">
|
||||||
<div v-for="(item, key) in itemsData" :key="key" class="relative mx-3 flex-shrink-0">
|
<div v-for="(item, key) in itemsData" :key="key" class="relative mx-3 flex-shrink-0" draggable="true" @dragstart="startDrag($event, key)" @dragover.prevent @dragenter.prevent @drop="onDrop($event, key)">
|
||||||
<!-- Image Display -->
|
<!-- Image Display -->
|
||||||
<template v-if="isImageFile(item.url)">
|
<template v-if="isImageFile(item.url)">
|
||||||
<a :href="item.url" :data-pswp-width="item.width" :data-pswp-height="item.height" target="_blank" rel="noreferrer" class="block">
|
<a :href="item.url" :data-pswp-width="item.width" :data-pswp-height="item.height" target="_blank" rel="noreferrer" class="block">
|
||||||
@@ -20,12 +20,12 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Download Button -->
|
<!-- Download Button -->
|
||||||
<Button icon="pi pi-download" severity="secondary" size="small" @click="downloadFile(item.url)" class="p-button-sm"> Download </Button>
|
<Button icon="pi pi-download" severity="secondary" size="small" @click="downloadFile(item.url)" class="p-button-sm"> دانلود </Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Remove Button -->
|
<!-- Remove Button -->
|
||||||
<button class="absolute right-4 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-white/80 text-red-500 hover:bg-white" @click="removeItem(key)">
|
<button class="absolute right-4 top-2 flex h-6 w-6 items-center justify-center rounded-full text-red-500" @click="removeItem(key, item.url)">
|
||||||
<font-awesome-icon icon="fa-solid fa-x" />
|
<font-awesome-icon icon="fa-solid fa-x" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,8 +84,12 @@ function downloadFile(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateItemsData(items) {
|
function updateItemsData(items) {
|
||||||
|
if (!items || !Array.isArray(items)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
if (isImageFile(item.url)) {
|
if (item && item.url && isImageFile(item.url)) {
|
||||||
// Handle images
|
// Handle images
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = item.url;
|
img.src = item.url;
|
||||||
@@ -97,7 +101,6 @@ function updateItemsData(items) {
|
|||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
galleryId: {
|
galleryId: {
|
||||||
@@ -133,11 +136,36 @@ watch(
|
|||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue', 'delete-image']);
|
||||||
|
|
||||||
function removeItem(index) {
|
function removeItem(index, url) {
|
||||||
const newItems = [...props.modelValue];
|
const newItems = [...props.modelValue];
|
||||||
newItems.splice(index, 1);
|
newItems.splice(index, 1);
|
||||||
emit('update:modelValue', newItems);
|
emit('update:modelValue', newItems);
|
||||||
|
emit('delete-image', url, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDrag(event, index) {
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
event.dataTransfer.setData('imageIndex', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(event, dropIndex) {
|
||||||
|
const dragIndex = event.dataTransfer.getData('imageIndex');
|
||||||
|
|
||||||
|
if (dragIndex === dropIndex.toString()) return;
|
||||||
|
|
||||||
|
// Create a copy of the images array
|
||||||
|
const newImages = [...props.modelValue];
|
||||||
|
|
||||||
|
// Remove the dragged item
|
||||||
|
const draggedItem = newImages.splice(dragIndex, 1)[0];
|
||||||
|
|
||||||
|
// Insert it at the new position
|
||||||
|
newImages.splice(dropIndex, 0, draggedItem);
|
||||||
|
|
||||||
|
// Update the model
|
||||||
|
emit('update:modelValue', newImages);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -80,30 +80,42 @@ const uploadedFiles = ref([...props.modelValue]);
|
|||||||
|
|
||||||
// Define FilePond component
|
// Define FilePond component
|
||||||
const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview);
|
const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview);
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue', 'upload', 'delete']);
|
||||||
|
|
||||||
// Handle server responses for file processing
|
// Handle server responses for file processing
|
||||||
function handleProcessResponse(response) {
|
function handleProcessResponse(response) {
|
||||||
|
if (props.mode === 'public') {
|
||||||
const data = JSON.parse(response);
|
const data = JSON.parse(response);
|
||||||
|
const publicUrl = data.data[0].fileRecord.publicUrl;
|
||||||
const fileName = data.data[0].fileRecord.fileName;
|
const fileName = data.data[0].fileRecord.fileName;
|
||||||
uploadedFiles.value.push(fileName);
|
uploadedFiles.value.push(publicUrl);
|
||||||
emit('update:modelValue', uploadedFiles);
|
emit('update:modelValue', uploadedFiles);
|
||||||
return fileName;
|
emit('upload', uploadedFiles);
|
||||||
|
return publicUrl;
|
||||||
|
} else {
|
||||||
|
const data = JSON.parse(response);
|
||||||
|
const privateUrl = data.data[0].fileRecord.privateUrl;
|
||||||
|
const fileName = data.data[0].fileRecord.fileName;
|
||||||
|
uploadedFiles.value.push(privateUrl);
|
||||||
|
emit('update:modelValue', uploadedFiles);
|
||||||
|
emit('upload', uploadedFiles);
|
||||||
|
return privateUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle server responses for file revert
|
// Handle server responses for file revert
|
||||||
function handleRevertResponse(response) {
|
function handleRevertResponse(response) {
|
||||||
const data = JSON.parse(response);
|
const data = JSON.parse(response);
|
||||||
const fileId = data.data.fileRecord.id;
|
const fileName = data.data;
|
||||||
|
|
||||||
// Find and remove the file name from uploadedFiles
|
// Find and remove the file name from uploadedFiles
|
||||||
const fileIndex = uploadedFiles.value.findIndex((name) => name === fileId);
|
const fileIndex = uploadedFiles.value.findIndex((name) => name === fileName);
|
||||||
if (fileIndex !== -1) {
|
if (fileIndex !== -1) {
|
||||||
uploadedFiles.value.splice(fileIndex, 1);
|
uploadedFiles.value.splice(fileIndex, 1);
|
||||||
emit('update:modelValue', uploadedFiles);
|
emit('update:modelValue', uploadedFiles);
|
||||||
|
emit('delete', uploadedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileId;
|
return fileName;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user