feat: add loading screens, skeleton states, and table loaders across all list pages
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,125 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'table' // 'table' | 'cards' | 'split' | 'spinner'
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Number,
|
||||||
|
default: 6
|
||||||
|
},
|
||||||
|
hasImage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showHeader: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: 'در حال بارگذاری اطلاعات...'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="list-loading-container w-full animate-pulse">
|
||||||
|
<!-- Optional Top Header Shimmer -->
|
||||||
|
<div v-if="showHeader" class="flex flex-wrap items-center justify-between gap-4 mb-4">
|
||||||
|
<Skeleton width="12rem" height="2rem" class="rounded-xl" />
|
||||||
|
<Skeleton width="16rem" height="2.5rem" class="rounded-xl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── TABLE SKELETON ── -->
|
||||||
|
<div
|
||||||
|
v-if="type === 'table'"
|
||||||
|
class="rounded-2xl border border-surface-200 dark:border-surface-700 overflow-hidden bg-surface-0 dark:bg-surface-900 shadow-sm"
|
||||||
|
>
|
||||||
|
<!-- Table Header Bar -->
|
||||||
|
<div class="p-4 bg-surface-50 dark:bg-surface-800/60 border-b border-surface-200 dark:border-surface-700 flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||||
|
<Skeleton width="10rem" height="1.5rem" class="rounded-lg" />
|
||||||
|
</div>
|
||||||
|
<Skeleton width="14rem" height="2.25rem" class="rounded-xl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table Rows -->
|
||||||
|
<div class="divide-y divide-surface-100 dark:divide-surface-800">
|
||||||
|
<div v-for="r in rows" :key="r" class="p-4 flex items-center gap-4">
|
||||||
|
<Skeleton width="1.75rem" height="1.75rem" class="rounded-md shrink-0" />
|
||||||
|
<Skeleton v-if="hasImage" width="3.5rem" height="3.5rem" class="rounded-xl shrink-0" />
|
||||||
|
<div class="flex-1 grid grid-cols-2 sm:grid-cols-4 gap-4 items-center">
|
||||||
|
<Skeleton width="75%" height="1.25rem" class="rounded-md" />
|
||||||
|
<Skeleton width="55%" height="1.25rem" class="rounded-md" />
|
||||||
|
<Skeleton width="65%" height="1.25rem" class="rounded-md hidden sm:block" />
|
||||||
|
<Skeleton width="45%" height="1.25rem" class="rounded-md hidden sm:block" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
|
<Skeleton width="2.25rem" height="2.25rem" class="rounded-full" />
|
||||||
|
<Skeleton width="2.25rem" height="2.25rem" class="rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── CARDS GRID SKELETON ── -->
|
||||||
|
<div v-else-if="type === 'cards'" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
<div
|
||||||
|
v-for="c in rows"
|
||||||
|
:key="c"
|
||||||
|
class="card p-4 rounded-2xl border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 shadow-sm flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<Skeleton width="100%" height="12rem" class="rounded-xl" />
|
||||||
|
<div class="flex items-center justify-between gap-2 mt-1">
|
||||||
|
<Skeleton width="60%" height="1.5rem" class="rounded-md" />
|
||||||
|
<Skeleton width="25%" height="1.25rem" class="rounded-full" />
|
||||||
|
</div>
|
||||||
|
<Skeleton width="40%" height="1rem" class="rounded-md" />
|
||||||
|
<div class="flex items-center justify-between pt-3 border-t border-surface-100 dark:border-surface-800 mt-auto">
|
||||||
|
<Skeleton width="30%" height="1.25rem" class="rounded-md" />
|
||||||
|
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── SPLIT VIEW SKELETON (Cards + Map Placeholder) ── -->
|
||||||
|
<div v-else-if="type === 'split'" class="grid grid-cols-1 lg:grid-cols-12 gap-6 h-[750px]">
|
||||||
|
<!-- Left Cards list skeleton -->
|
||||||
|
<div class="lg:col-span-7 flex flex-col gap-4 overflow-hidden">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<Skeleton width="10rem" height="1.5rem" class="rounded-md" />
|
||||||
|
<Skeleton width="6rem" height="1.25rem" class="rounded-md" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
v-for="i in 4"
|
||||||
|
:key="i"
|
||||||
|
class="p-4 rounded-2xl border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 shadow-sm flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<Skeleton width="100%" height="10rem" class="rounded-xl" />
|
||||||
|
<Skeleton width="70%" height="1.25rem" class="rounded-md" />
|
||||||
|
<Skeleton width="45%" height="1rem" class="rounded-md" />
|
||||||
|
<div class="flex justify-between items-center pt-2 border-t border-surface-100 dark:border-surface-800">
|
||||||
|
<Skeleton width="35%" height="1rem" class="rounded-md" />
|
||||||
|
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Map skeleton -->
|
||||||
|
<div class="lg:col-span-5 h-full rounded-2xl border border-surface-200 dark:border-surface-700 overflow-hidden bg-surface-100 dark:bg-surface-800 flex flex-col items-center justify-center p-8 text-center">
|
||||||
|
<i class="pi pi-map text-5xl text-surface-400 dark:text-surface-600 mb-4 animate-bounce"></i>
|
||||||
|
<Skeleton width="12rem" height="1.5rem" class="rounded-md mb-2" />
|
||||||
|
<Skeleton width="8rem" height="1rem" class="rounded-md" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── SPINNER / OVERLAY SKELETON ── -->
|
||||||
|
<div v-else class="flex flex-col items-center justify-center py-16 gap-4">
|
||||||
|
<ProgressSpinner style="width: 48px; height: 48px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500 dark:text-surface-400">{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,8 +5,22 @@ import { CategoryService } from '@/service/CategoryService';
|
|||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const dataLoading = ref(true);
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
dataLoading.value = true;
|
||||||
|
try {
|
||||||
|
categories.value = await CategoryService.getAllCategories();
|
||||||
|
} catch (err) {
|
||||||
|
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در دریافت اطلاعات', life: 3000 });
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
CategoryService.getAllCategories().then((data) => (categories.value = data));
|
loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
const dt = ref();
|
const dt = ref();
|
||||||
@@ -21,6 +35,7 @@ const filters = ref({
|
|||||||
});
|
});
|
||||||
const submitted = ref(false);
|
const submitted = ref(false);
|
||||||
|
|
||||||
|
|
||||||
function openNew() {
|
function openNew() {
|
||||||
category.value = {};
|
category.value = {};
|
||||||
submitted.value = false;
|
submitted.value = false;
|
||||||
@@ -115,6 +130,7 @@ function deleteSelectedCategories() {
|
|||||||
ref="dt"
|
ref="dt"
|
||||||
v-model:selection="selectedCategories"
|
v-model:selection="selectedCategories"
|
||||||
:value="categories"
|
:value="categories"
|
||||||
|
:loading="dataLoading"
|
||||||
dataKey="id"
|
dataKey="id"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
:rows="20"
|
:rows="20"
|
||||||
@@ -123,9 +139,22 @@ function deleteSelectedCategories() {
|
|||||||
:rowsPerPageOptions="[20, 40, 80]"
|
:rowsPerPageOptions="[20, 40, 80]"
|
||||||
currentPageReportTemplate="نمایش {first} تا {last} از {totalRecords} دستهبندی"
|
currentPageReportTemplate="نمایش {first} تا {last} از {totalRecords} دستهبندی"
|
||||||
>
|
>
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری دستهبندیها...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div v-if="!dataLoading" class="p-8 text-center text-surface-500">
|
||||||
|
<i class="pi pi-inbox text-4xl mb-2 text-surface-400"></i>
|
||||||
|
<p>هیچ دستهبندی یافت نشد.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-wrap gap-2 items-center justify-between">
|
<div class="flex flex-wrap gap-2 items-center justify-between">
|
||||||
<h4 class="m-0">مدیریت دسته بندی ها</h4>
|
<h4 class="m-0">مدیریت دسته بندی ها</h4>
|
||||||
|
|
||||||
<IconField>
|
<IconField>
|
||||||
<InputIcon>
|
<InputIcon>
|
||||||
<i class="pi pi-search" />
|
<i class="pi pi-search" />
|
||||||
|
|||||||
@@ -12,11 +12,23 @@ const selectedFeature = ref();
|
|||||||
const submitted = ref(false);
|
const submitted = ref(false);
|
||||||
const feature = ref({});
|
const feature = ref({});
|
||||||
const categories = ref([]);
|
const categories = ref([]);
|
||||||
const featureDialog = ref(false);
|
const dataLoading = ref(true);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
categories.value = await CategoryService.getAllCategories();
|
dataLoading.value = true;
|
||||||
|
try {
|
||||||
|
categories.value = await CategoryService.getAllCategories();
|
||||||
|
if (categories.value.length > 0 && !selectedTab.value) {
|
||||||
|
selectedTab.value = categories.value[0]._id;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const featureType = [
|
const featureType = [
|
||||||
{
|
{
|
||||||
value: 'string',
|
value: 'string',
|
||||||
@@ -77,12 +89,24 @@ const saveFeature = async () => {
|
|||||||
<template>
|
<template>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div class="font-semibold text-xl mb-4">ویژگی دسته بندی ها</div>
|
<div class="font-semibold text-xl mb-4">ویژگی دسته بندی ها</div>
|
||||||
<Tabs :value="selectedTab" :pt="{}">
|
|
||||||
|
<div v-if="dataLoading" class="space-y-4">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Skeleton v-for="i in 4" :key="i" width="8rem" height="2.5rem" class="rounded-xl" />
|
||||||
|
</div>
|
||||||
|
<div class="w-1/3 my-4 space-y-2">
|
||||||
|
<Skeleton width="100%" height="15rem" class="rounded-xl" />
|
||||||
|
<Skeleton width="6rem" height="2.5rem" class="rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs v-else :value="selectedTab" :pt="{}">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab v-for="category in categories" :key="category._id" :value="category._id">
|
<Tab v-for="category in categories" :key="category._id" :value="category._id">
|
||||||
{{ category.nativeName }}
|
{{ category.nativeName }}
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel v-for="category in categories" :key="category._id + '_content'" :value="category._id">
|
<TabPanel v-for="category in categories" :key="category._id + '_content'" :value="category._id">
|
||||||
<p>لیست ویژگی های {{ category.nativeName }}</p>
|
<p>لیست ویژگی های {{ category.nativeName }}</p>
|
||||||
|
|||||||
@@ -124,7 +124,10 @@ const onPageChange = (event) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataLoading = ref(true);
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
dataLoading.value = true;
|
||||||
try {
|
try {
|
||||||
if (searchMethod.value === null) {
|
if (searchMethod.value === null) {
|
||||||
await loadSearchSetting();
|
await loadSearchSetting();
|
||||||
@@ -141,10 +144,13 @@ const loadData = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در بازیابی اطلاعات املاک', life: 3000 });
|
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در بازیابی اطلاعات املاک', life: 3000 });
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error);
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const indexingFinished = ref(false);
|
const indexingFinished = ref(false);
|
||||||
const indexingMessage = ref('در حال ایندکس...');
|
const indexingMessage = ref('در حال ایندکس...');
|
||||||
@@ -322,6 +328,7 @@ const onCardHover = (propertyId) => {
|
|||||||
ref="dt"
|
ref="dt"
|
||||||
v-model:selection="selectedProperties"
|
v-model:selection="selectedProperties"
|
||||||
:value="properties"
|
:value="properties"
|
||||||
|
:loading="dataLoading"
|
||||||
dataKey="_id"
|
dataKey="_id"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
:rows="per_page"
|
:rows="per_page"
|
||||||
@@ -335,12 +342,25 @@ const onCardHover = (propertyId) => {
|
|||||||
stripedRows
|
stripedRows
|
||||||
responsiveLayout="scroll"
|
responsiveLayout="scroll"
|
||||||
>
|
>
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری اطلاعات املاک...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div v-if="!dataLoading" class="p-8 text-center text-surface-500">
|
||||||
|
<i class="pi pi-inbox text-4xl mb-2 text-surface-400"></i>
|
||||||
|
<p>هیچ ملکی یافت نشد.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-wrap gap-2 items-center justify-between">
|
<div class="flex flex-wrap gap-2 items-center justify-between">
|
||||||
<h4 class="m-0 text-lg font-bold">لیست املاک ({{ totalCount }} مورد)</h4>
|
<h4 class="m-0 text-lg font-bold">لیست املاک ({{ totalCount }} مورد)</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
|
||||||
<Column header="تصویر" style="width: 5rem">
|
<Column header="تصویر" style="width: 5rem">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
@@ -423,8 +443,25 @@ const onCardHover = (propertyId) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Skeleton in Split View -->
|
||||||
|
<div v-if="dataLoading" class="cards-grid">
|
||||||
|
<div
|
||||||
|
v-for="i in 6"
|
||||||
|
:key="i"
|
||||||
|
class="p-4 rounded-2xl border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 shadow-sm flex flex-col gap-3 animate-pulse"
|
||||||
|
>
|
||||||
|
<Skeleton width="100%" height="11rem" class="rounded-xl" />
|
||||||
|
<Skeleton width="70%" height="1.25rem" class="rounded-md" />
|
||||||
|
<Skeleton width="45%" height="1rem" class="rounded-md" />
|
||||||
|
<div class="flex justify-between items-center pt-2 border-t border-surface-100 dark:border-surface-800">
|
||||||
|
<Skeleton width="35%" height="1rem" class="rounded-md" />
|
||||||
|
<Skeleton width="2rem" height="2rem" class="rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Cards Grid -->
|
<!-- Cards Grid -->
|
||||||
<div v-if="properties.length > 0" class="cards-grid">
|
<div v-else-if="properties.length > 0" class="cards-grid">
|
||||||
<div
|
<div
|
||||||
v-for="prop in properties"
|
v-for="prop in properties"
|
||||||
:key="prop._id || prop.id"
|
:key="prop._id || prop.id"
|
||||||
@@ -505,9 +542,11 @@ const onCardHover = (propertyId) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="p-8 text-center text-slate-500">
|
<div v-else class="p-8 text-center text-slate-500">
|
||||||
ملکی یافت نشد.
|
<i class="pi pi-inbox text-4xl mb-2 text-surface-400"></i>
|
||||||
|
<p>ملکی یافت نشد.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<Paginator
|
<Paginator
|
||||||
:rows="per_page"
|
:rows="per_page"
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ const confirm = useConfirm();
|
|||||||
|
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
const allPermissions = ref([]);
|
const allPermissions = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
|
|
||||||
const editMode = ref(false);
|
const editMode = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const selectedRole = ref(null);
|
const selectedRole = ref(null);
|
||||||
@@ -253,14 +254,22 @@ const confirmDelete = (r, event) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری نقشها و دسترسیها...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="empty-state">
|
<div v-if="!loading" class="empty-state">
|
||||||
<i class="pi pi-shield empty-icon"></i>
|
<i class="pi pi-shield empty-icon"></i>
|
||||||
<p>هیچ نقشی تعریف نشده است</p>
|
<p>هیچ نقشی تعریف نشده است</p>
|
||||||
<Button label="افزودن اولین نقش" icon="pi pi-plus" @click="openCreate" />
|
<Button label="افزودن اولین نقش" icon="pi pi-plus" @click="openCreate" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<!-- Native Name -->
|
<!-- Native Name -->
|
||||||
<Column field="nativeName" header="نام فارسی نقش" style="min-width: 180px">
|
<Column field="nativeName" header="نام فارسی نقش" style="min-width: 180px">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
|
|||||||
@@ -121,7 +121,10 @@ const onPageChange = (event) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataLoading = ref(true);
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
dataLoading.value = true;
|
||||||
try {
|
try {
|
||||||
if (searchMethod.value === null) {
|
if (searchMethod.value === null) {
|
||||||
await loadSearchSetting();
|
await loadSearchSetting();
|
||||||
@@ -138,10 +141,13 @@ const loadData = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: 'خطا در دریافت اطلاعات', life: 3000 });
|
toast.add({ severity: 'error', summary: 'Error', detail: 'خطا در دریافت اطلاعات', life: 3000 });
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error);
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const indexingFinished = ref(false);
|
const indexingFinished = ref(false);
|
||||||
const indexingMessage = ref('در حال ایندکس...');
|
const indexingMessage = ref('در حال ایندکس...');
|
||||||
@@ -277,6 +283,7 @@ const toggle = (event, data) => {
|
|||||||
ref="dt"
|
ref="dt"
|
||||||
v-model:selection="selectedTransactions"
|
v-model:selection="selectedTransactions"
|
||||||
:value="transactions"
|
:value="transactions"
|
||||||
|
:loading="dataLoading"
|
||||||
dataKey="id"
|
dataKey="id"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
:rows="per_page"
|
:rows="per_page"
|
||||||
@@ -292,6 +299,19 @@ const toggle = (event, data) => {
|
|||||||
filterDisplay="menu"
|
filterDisplay="menu"
|
||||||
:globalFilterFields="['type', 'status', 'belongingName', 'createdAt', 'description']"
|
:globalFilterFields="['type', 'status', 'belongingName', 'createdAt', 'description']"
|
||||||
>
|
>
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری تراکنشها...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div v-if="!dataLoading" class="p-8 text-center text-surface-500">
|
||||||
|
<i class="pi pi-inbox text-4xl mb-2 text-surface-400"></i>
|
||||||
|
<p>هیچ تراکنشی یافت نشد.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ const confirm = useConfirm();
|
|||||||
|
|
||||||
const users = ref([]);
|
const users = ref([]);
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
|
|
||||||
const editMode = ref(false);
|
const editMode = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const selectedUser = ref(null);
|
const selectedUser = ref(null);
|
||||||
@@ -266,14 +267,22 @@ const getInitials = (name, email) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری کاربران...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="empty-state">
|
<div v-if="!loading" class="empty-state">
|
||||||
<i class="pi pi-user-minus empty-icon"></i>
|
<i class="pi pi-user-minus empty-icon"></i>
|
||||||
<p>هیچ کاربری یافت نشد</p>
|
<p>هیچ کاربری یافت نشد</p>
|
||||||
<Button label="افزودن اولین کاربر" icon="pi pi-plus" @click="openCreate" />
|
<Button label="افزودن اولین کاربر" icon="pi pi-plus" @click="openCreate" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<!-- User Column -->
|
<!-- User Column -->
|
||||||
<Column header="کاربر" style="min-width: 200px">
|
<Column header="کاربر" style="min-width: 200px">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ const toast = useToast();
|
|||||||
|
|
||||||
const transactions = ref([]);
|
const transactions = ref([]);
|
||||||
const totalCount = ref(0);
|
const totalCount = ref(0);
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
|
|
||||||
const perPage = ref(30);
|
const perPage = ref(30);
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
@@ -196,13 +197,21 @@ const formatDate = (d) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری تراکنشهای انبار...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="empty-state">
|
<div v-if="!loading" class="empty-state">
|
||||||
<i class="pi pi-inbox"></i>
|
<i class="pi pi-inbox"></i>
|
||||||
<p>هیچ تراکنشی یافت نشد</p>
|
<p>هیچ تراکنشی یافت نشد</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<Column header="نوع" style="width:7%">
|
<Column header="نوع" style="width:7%">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Tag
|
<Tag
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ const totalCount = ref(0);
|
|||||||
const page = ref(Number(route.query.page) || 1);
|
const page = ref(Number(route.query.page) || 1);
|
||||||
const perPage = ref(Number(route.query.per_page) || 20);
|
const perPage = ref(Number(route.query.per_page) || 20);
|
||||||
const searchPhrase = ref(route.query.search || '');
|
const searchPhrase = ref(route.query.search || '');
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
|
|
||||||
|
|
||||||
// Create/Edit dialog
|
// Create/Edit dialog
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
@@ -217,14 +218,22 @@ const viewStock = (itemId) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری اقلام انبار...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="empty-state">
|
<div v-if="!loading" class="empty-state">
|
||||||
<i class="pi pi-inbox empty-icon"></i>
|
<i class="pi pi-inbox empty-icon"></i>
|
||||||
<p>هیچ قلمی یافت نشد</p>
|
<p>هیچ قلمی یافت نشد</p>
|
||||||
<Button label="افزودن اولین قلم" icon="pi pi-plus" @click="openCreateDialog" />
|
<Button label="افزودن اولین قلم" icon="pi pi-plus" @click="openCreateDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<Column field="code" header="کد" style="width:8%">
|
<Column field="code" header="کد" style="width:8%">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<span class="code-badge" v-if="data.code">{{ data.code }}</span>
|
<span class="code-badge" v-if="data.code">{{ data.code }}</span>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ const toast = useToast();
|
|||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
|
||||||
const warehouses = ref([]);
|
const warehouses = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(true);
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
|
|
||||||
const editMode = ref(false);
|
const editMode = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const selectedWarehouse = ref(null);
|
const selectedWarehouse = ref(null);
|
||||||
@@ -143,14 +144,22 @@ const onSearch = () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 gap-3">
|
||||||
|
<ProgressSpinner style="width: 42px; height: 42px" strokeWidth="4" />
|
||||||
|
<span class="text-sm font-medium text-surface-500">در حال بارگذاری انبارها...</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="empty-state">
|
<div v-if="!loading" class="empty-state">
|
||||||
<i class="pi pi-map empty-icon"></i>
|
<i class="pi pi-map empty-icon"></i>
|
||||||
<p>هیچ انباری تعریف نشده است</p>
|
<p>هیچ انباری تعریف نشده است</p>
|
||||||
<Button label="افزودن اولین انبار" icon="pi pi-plus" @click="openCreate" />
|
<Button label="افزودن اولین انبار" icon="pi pi-plus" @click="openCreate" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<Column field="code" header="کد" style="width:8%">
|
<Column field="code" header="کد" style="width:8%">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<span v-if="data.code" class="code-badge">{{ data.code }}</span>
|
<span v-if="data.code" class="code-badge">{{ data.code }}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user