complete Complete User Request Submission section RAD-1
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<script setup>
|
||||
import {Link} from '@inertiajs/inertia-vue3';
|
||||
import {useAuthStore} from "../../store/Auth.js";
|
||||
|
||||
const AuthStore = useAuthStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white px-6 h-[100vh] flex items-center">
|
||||
<div class="p-6 bg-white rounded-3xl shadow-all w-full xl:!w-1/3 mx-auto">
|
||||
<div class="text-center mb-8">
|
||||
<div class="text-client-Fontcolor text-2xl font-extrabold mb-4">ورود به حساب کاربری</div>
|
||||
<span class="text-client-Fontcolor text-xs font-black leading-normal">قبلا حساب ساختی؟</span>
|
||||
<Link href="/auth/register" class="font-medium no-underline mr-2 text-xs text-client-Fontcolor cursor-pointer">ایجاد حساب جدید</Link>
|
||||
</div>
|
||||
<div class="text-white grid pb-6 grid-cols-2 gap-2">
|
||||
<a class="border-[1px] bg-[#4875e9] flex justify-between px-4 items-center rounded-lg h-11">
|
||||
<span class="text-sm font-normal">ورود با گوگل</span>
|
||||
<i class="fi fi-brands-google flex"></i>
|
||||
</a>
|
||||
<a class="border-[1px] flex items-center px-4 justify-between bg-client-sidebar-background rounded-lg h-11">
|
||||
<span class="text-sm font-normal">ورود با گیتهاب</span>
|
||||
<i class="fi fi-brands-github flex"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] gap-2 h-10 mb-6 items-center">
|
||||
<div class="h-[1px] bg-client-gray/10"></div>
|
||||
<div class="text-client-Fontcolor/90 px-4">یا</div>
|
||||
<div class="h-[1px] bg-client-gray/10"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<FloatLabel variant="on"
|
||||
class="!w-full">
|
||||
<InputText id="email1" type="text"
|
||||
appendTo="self"
|
||||
v-model="AuthStore.loginForm.email"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !text-client-Fontcolor w-full"/>
|
||||
<label for="email1" class="!text-client-Fontcolor !text-sm !bg-white font-light">ایمیل</label>
|
||||
</FloatLabel>
|
||||
<Message v-if="AuthStore.errorsLoginForm.email"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{AuthStore.errorsLoginForm.email[0]}}</Message>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FloatLabel variant="on"
|
||||
class="!w-full">
|
||||
<Password id="password" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask v-model="AuthStore.loginForm.password"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !text-client-Fontcolor "/>
|
||||
<label for="on_label" class="!text-client-Fontcolor !bg-white !text-sm font-light">کلمه عبور</label>
|
||||
</FloatLabel>
|
||||
<Message v-if="AuthStore.errorsLoginForm.password"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{AuthStore.errorsLoginForm.password[0]}}</Message>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2a items-center justify-start mb-12">
|
||||
<div class="flex items-center gap-2 !text-client-Fontcolor">
|
||||
<Checkbox inputId="remember1"
|
||||
class="custom-checkbox"
|
||||
v-model="AuthStore.loginForm.remember"
|
||||
binary />
|
||||
<label class="!text-sm cursor-pointer !font-normal" for="remember1">مرا به خاطر بسپار</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button @click="AuthStore.login()" :loading="AuthStore.loaderLoginForm" label="ورود به سایت" icon="pi pi-user !text-md flex !leading-none" severity="secondary"
|
||||
class="w-1/2 m-auto flex flex-row-reverse items-center !font-normal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="css">
|
||||
.p-password{
|
||||
@apply !w-full
|
||||
}
|
||||
.p-password-input{
|
||||
@apply !w-full
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import {useAuthStore} from "../../store/Auth";
|
||||
|
||||
const AuthStore = useAuthStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white px-6 h-[100vh] flex items-center">
|
||||
<div class="p-6 bg-white rounded-3xl shadow-all w-full xl:!w-1/3 m-auto">
|
||||
<div class="text-center py-4 mb-8">
|
||||
<div class="text-client-Fontcolor text-2xl font-extrabold mb-4">عضویت در سایت</div>
|
||||
<span class="text-client-Fontcolor text-xs font-black leading-normal">قبلا حساب ساختی؟</span>
|
||||
<Link href="/auth/login" class="font-medium no-underline mr-2 text-xs text-client-Fontcolor cursor-pointer">ورود به سایت</Link>
|
||||
</div>
|
||||
<div class="text-white grid pb-6 grid-cols-2 gap-2">
|
||||
<a class="border-[1px] bg-[#4875e9] flex justify-between px-4 items-center rounded-lg h-11">
|
||||
<span class="text-sm font-normal">ورود با گوگل</span>
|
||||
<i class="pi pi-google"></i>
|
||||
</a>
|
||||
<a class="border-[1px] flex items-center px-4 justify-between bg-client-sidebar-background rounded-lg h-11">
|
||||
<span class="text-sm font-normal">ورود با گیتهاب</span>
|
||||
<i class="pi pi-github"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] gap-2 h-10 mb-6 items-center ">
|
||||
<div class="h-[1px] bg-client-gray/10"></div>
|
||||
<div class="text-client-Fontcolor/90 px-4">یا</div>
|
||||
<div class="h-[1px] bg-client-gray/10"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<FloatLabel variant="on"
|
||||
class="!w-full">
|
||||
<InputText id="username" type="text"
|
||||
appendTo="self"
|
||||
v-model="AuthStore.registerForm.name"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/30 !text-client-Fontcolor w-full"/>
|
||||
<label for="username" class="!text-client-Fontcolor !text-sm !bg-white font-light">نام کاربری</label>
|
||||
</FloatLabel>
|
||||
<Message v-if="AuthStore.errorsRegisterForm.name"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{AuthStore.errorsRegisterForm.name[0]}}</Message>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FloatLabel variant="on"
|
||||
class="!w-full">
|
||||
<InputText id="email1" type="text"
|
||||
appendTo="self"
|
||||
v-model="AuthStore.registerForm.email"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/30 !text-client-Fontcolor w-full"/>
|
||||
<label for="email1" class="!text-client-Fontcolor !text-sm !bg-white font-light">ایمیل</label>
|
||||
</FloatLabel>
|
||||
<Message v-if="AuthStore.errorsRegisterForm.email"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{AuthStore.errorsRegisterForm.email[0]}}</Message>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FloatLabel variant="on" class="!w-full">
|
||||
<Password id="password" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask
|
||||
v-model="AuthStore.registerForm.password"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/20 !text-client-Fontcolor w-full"/>
|
||||
<label for="on_label" class="!text-client-Fontcolor !bg-white !text-sm font-light">کلمه عبور</label>
|
||||
</FloatLabel>
|
||||
<Message v-if="AuthStore.errorsRegisterForm.password"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{AuthStore.errorsRegisterForm.password[0]}}</Message>
|
||||
</div>
|
||||
|
||||
|
||||
<FloatLabel variant="on" class="!w-full mb-6">
|
||||
<Password id="password_confirmation" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask
|
||||
v-model="AuthStore.registerForm.password_confirmation"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/20 !text-client-Fontcolor w-full"/>
|
||||
<label for="password_confirmation" class="!text-client-Fontcolor !bg-white !text-sm font-light">تکرار کلمه عبور</label>
|
||||
</FloatLabel>
|
||||
|
||||
|
||||
<Button label="عضویت در سایت" @click="AuthStore.register()" :loading="AuthStore.loaderRegisterForm" icon="pi pi-user !text-md flex !leading-none" severity="secondary"
|
||||
class="w-1/2 m-auto flex flex-row-reverse items-center !font-normal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="css">
|
||||
.p-password{
|
||||
@apply !w-full
|
||||
}
|
||||
.p-password-input{
|
||||
@apply !w-full
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,194 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {onMounted , onUnmounted} from 'vue';
|
||||
import {useDocumentStore} from "../../store/Document.js";
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import GlobalSelectMedium from "../../components/medium/GlobalSelectMedium.vue";
|
||||
import Card from "../../components/ui/Card.vue";
|
||||
import UISelect from "../../components/ui/UISelect.vue";
|
||||
|
||||
const documentStore = useDocumentStore();
|
||||
|
||||
const props = defineProps({
|
||||
is_edit: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
document: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
pets: {
|
||||
type: Object,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
documentStore.petOptions = props.pets;
|
||||
if (props.is_edit) {
|
||||
documentStore.setEditData(props.document);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
documentStore.resetCreateForm();
|
||||
documentStore.is_edit_form = false;
|
||||
documentStore.errorsCreateForm = [];
|
||||
})
|
||||
|
||||
const removePrimaryFile = (index) => {
|
||||
documentStore.createForm.primary_files.splice(index, 1);
|
||||
}
|
||||
|
||||
const removeHistoryFile = (index) => {
|
||||
documentStore.createForm.history_files.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Card class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ documentStore.pageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="documentStore.endpoints.index">
|
||||
<span class="text-sm">{{ documentStore.indexPageTitle }}</span>
|
||||
<i class="fi fi-rr-list flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<section class="grid w-full grid-cols-1 lg:grid-cols-[1fr_22rem] gap-4">
|
||||
<Card>
|
||||
<div class="rounded-2xl bg-white">
|
||||
<Form ref="formRef" v-slot="$form" class=" p-4 grid grid-cols-2 gap-4 !w-full">
|
||||
<FormField v-slot="$field" name="modality" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نوع درخواست</label>
|
||||
<UISelect
|
||||
placeholder="انتخاب نوع درخواست"
|
||||
v-model="documentStore.createForm.modality"
|
||||
:options="documentStore.modalityOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
/>
|
||||
<Message v-if="documentStore.errorsCreateForm?.modality" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.modality[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="priority" initialValue="" class="flex flex-col gap-1">
|
||||
<label>اولویت</label>
|
||||
<UISelect
|
||||
placeholder="انتخاب اولویت"
|
||||
v-model="documentStore.createForm.priority"
|
||||
:options="documentStore.priorityOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
/>
|
||||
<Message v-if="documentStore.errorsCreateForm?.priority" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.priority[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="status" initialValue="" class="flex flex-col gap-1">
|
||||
<label>وضعیت</label>
|
||||
<UISelect
|
||||
placeholder="انتخاب وضعیت"
|
||||
v-model="documentStore.createForm.status"
|
||||
:options="documentStore.statusOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
/>
|
||||
<Message v-if="documentStore.errorsCreateForm?.status" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.status[0] }}</Message>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="$field" name="pet_id" initialValue="" class="flex flex-col gap-1">
|
||||
<label>انتخاب کیس</label>
|
||||
<UISelect
|
||||
placeholder="انتخاب کیس"
|
||||
v-model="documentStore.createForm.pet_id"
|
||||
:options="documentStore.petOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
:filter="true"
|
||||
searchUrl="/admin/pets-options"
|
||||
searchParam="search"
|
||||
@update:options="documentStore.petOptions = $event"
|
||||
/>
|
||||
<Message v-if="documentStore.errorsCreateForm?.pet_id" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.pet_id[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="submitted_at" initialValue="" class="flex flex-col gap-1">
|
||||
<label>تاریخ ثبت</label>
|
||||
<CustomDatePicker v-model="documentStore.createForm.submitted_at" class="!text-xs" type="datetime" compact-time></CustomDatePicker>
|
||||
<Message v-if="documentStore.errorsCreateForm?.submitted_at" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.submitted_at[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="processing_deadline" initialValue="" class="flex flex-col gap-1">
|
||||
<label>مهلت پردازش</label>
|
||||
<CustomDatePicker v-model="documentStore.createForm.processing_deadline" class="!text-xs" type="datetime" compact-time></CustomDatePicker>
|
||||
<Message v-if="documentStore.errorsCreateForm?.processing_deadline" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.processing_deadline[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="note" initialValue="" class="flex flex-col col-span-2 gap-1">
|
||||
<label>متن درخواست</label>
|
||||
<Textarea type="text" class="placeholder:!text-sm" v-model="documentStore.createForm.note" placeholder="متن درخواست را وارد کنید" />
|
||||
</FormField>
|
||||
<div class="grid grid-cols-2 col-span-2 gap-4">
|
||||
<Card class="mt-4">
|
||||
<div class="p-4 rounded-2xl pb-8">
|
||||
<h5>فایلهای اصلی</h5>
|
||||
<global-select-medium v-model="documentStore.createForm.primary_files" :multiple="true" @remove-item="removePrimaryFile" upload-endpoint="/admin/documents-upload"></global-select-medium>
|
||||
<Message v-if="documentStore.errorsCreateForm?.primary_files" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.primary_files[0] }}</Message>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<div class="p-4 rounded-2xl pb-8">
|
||||
<h5>فایلهای سابقه</h5>
|
||||
<global-select-medium v-model="documentStore.createForm.history_files" :multiple="true" @remove-item="removeHistoryFile" upload-endpoint="/admin/documents-upload"></global-select-medium>
|
||||
<Message v-if="documentStore.errorsCreateForm?.history_files" severity="error" size="small" variant="simple">{{ documentStore.errorsCreateForm?.history_files[0] }}</Message>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="gap-4">
|
||||
<Card class="grid grid-cols-1 content-start justify-start ">
|
||||
<div class="p-4 grid grid-cols-1 gap-4 rounded-2xl">
|
||||
<div class="grid grid-cols-1 justify-between items-center">
|
||||
<span>زمان انتشار:</span>
|
||||
<CustomDatePicker v-model="documentStore.createForm.datetime" class="!text-xs mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 justify-start">
|
||||
<Button :loading="documentStore.loaderCreateForm" @click="documentStore.update_and_store" type="button" severity="secondary" :label="documentStore.buttonLabel" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.p-multiselect {
|
||||
height: auto;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.p-multiselect-label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-header {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-filter {
|
||||
max-height: calc(100px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-item {
|
||||
font-size: 0.875rem;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,301 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import { ref, reactive } from 'vue';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import { Link } from "@inertiajs/inertia-vue3";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useDocumentStore } from "../../store/Document.js";
|
||||
import { useUserStore } from "../../store/User.js";
|
||||
import { usePetStore } from "../../store/Pet.js";
|
||||
const documentStore = useDocumentStore();
|
||||
const toast = useToast();
|
||||
const userStore = useUserStore();
|
||||
const petStore = usePetStore();
|
||||
const props = defineProps({
|
||||
documents: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
status: {
|
||||
type: String
|
||||
},
|
||||
priority: {
|
||||
type: String
|
||||
},
|
||||
pet: {
|
||||
type: String
|
||||
},
|
||||
permissions_edit: {
|
||||
type: Boolean
|
||||
},
|
||||
permissions_delete: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
|
||||
const documents = ref(props.documents ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
const filterLoader = ref(false);
|
||||
|
||||
const columns = ref(documentStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? '',
|
||||
status: props.status ?? '',
|
||||
priority: props.priority ?? '',
|
||||
pet: props.pet ?? '',
|
||||
})
|
||||
|
||||
const filters = ref({
|
||||
status: props.status ?? '',
|
||||
priority: props.priority ?? '',
|
||||
pet: props.pet ?? '',
|
||||
})
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
const onPageChange = (event) => {
|
||||
documentStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
|
||||
const searchParams = {
|
||||
...(queryParams.search && { search: queryParams.search }),
|
||||
...(queryParams.status && { status: queryParams.status }),
|
||||
...(queryParams.priority && { priority: queryParams.priority }),
|
||||
...(queryParams.pet && { pet: queryParams.pet }),
|
||||
...documentStore.convertFiltersToArrayParams(filters.value)
|
||||
};
|
||||
documentStore.search(searchParams);
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
const exportParams = {
|
||||
...(queryParams.search && { search: queryParams.search }),
|
||||
...(queryParams.status && { status: queryParams.status }),
|
||||
...(queryParams.priority && { priority: queryParams.priority }),
|
||||
...(queryParams.pet && { pet: queryParams.pet }),
|
||||
...documentStore.convertFiltersToArrayParams(filters.value)
|
||||
};
|
||||
documentStore.exportData(exportParams);
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
documentStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
documentStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const onFilterChange = (newFilters) => {
|
||||
filters.value = { ...newFilters };
|
||||
Object.keys(newFilters).forEach(key => {
|
||||
if (Array.isArray(newFilters[key])) {
|
||||
queryParams[key] = newFilters[key].join(',');
|
||||
} else {
|
||||
queryParams[key] = newFilters[key] || '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyFilters = () => {
|
||||
filterLoader.value = true;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
filters.value = {
|
||||
status: null,
|
||||
priority: null,
|
||||
pet: null,
|
||||
};
|
||||
queryParams.status = '';
|
||||
queryParams.priority = '';
|
||||
queryParams.pet = '';
|
||||
searchData();
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ documentStore.indexPageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="documentStore.endpoints.create">
|
||||
<span class="text-sm">{{ documentStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="documents.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="documents.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
:filters="filters"
|
||||
:filterLoader="filterLoader"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
@filter-change="onFilterChange"
|
||||
@clear-filters="clearFilters"
|
||||
@apply-filters="applyFilters"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
v-if="!permissions_delete"
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(document => document.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-status="{ data }">
|
||||
<Tag
|
||||
class="!text-xs"
|
||||
:value="data.status"
|
||||
:severity="documentStore.getStatusSeverity(data.status)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #column-priority="{ data }">
|
||||
<Tag
|
||||
class="!text-xs"
|
||||
:value="data.priority"
|
||||
:severity="documentStore.getPrioritySeverity(data.priority)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #column-requester="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<span class="text-right text-sm">{{ data.requester?.fullname }}</span>
|
||||
<Link :href="userStore.endpoints.edit(data.requester?.id)" class="text-sm">
|
||||
<i class="fi fi-rr-eye flex !text-xs !text-client-Fontcolor/40"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-pet="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<span class="text-right text-sm">{{ data.pet?.name }}</span>
|
||||
<Link :href="petStore.endpoints.edit(data.pet?.id)" class="text-sm">
|
||||
<i class="fi fi-rr-eye flex !text-xs !text-client-Fontcolor/40"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<Button
|
||||
v-if="!permissions_delete"
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button v-if="!permissions_edit" severity="info" variant="outlined" asChild v-slot="slotProps">
|
||||
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="documentStore.endpoints.edit(data.id)">
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import Master from "../layouts/master.vue";
|
||||
import AddMedia from "../components/medium/AddMedia.vue";
|
||||
import ListMedia from "../components/medium/ListMedia.vue";
|
||||
import {onMounted} from "vue";
|
||||
import {useMediumStore} from "../store/Medium.js";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
medium : {
|
||||
type: Object,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const MediumStore = useMediumStore();
|
||||
|
||||
onMounted(() => {
|
||||
MediumStore.mediums = props.medium?.data ?? [];
|
||||
MediumStore.paginate.total = props.medium?.total ?? null;
|
||||
MediumStore.paginate.last_page = props.medium?.last_page ?? null;
|
||||
MediumStore.paginate.current_page = props.medium?.current_page ?? null;
|
||||
MediumStore.paginate.links = props.medium?.links ?? null;
|
||||
MediumStore.paginate.per_page = props.medium?.per_page ?? null;
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<add-media></add-media>
|
||||
<list-media></list-media>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {ref , onMounted , onUnmounted} from 'vue';
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import {usePermissionStore} from "../../store/Permission.js";
|
||||
import Card from "../../components/ui/Card.vue";
|
||||
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const props = defineProps({
|
||||
is_edit : {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
permission: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.is_edit) {
|
||||
permissionStore.setEditData(props.permission);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
permissionStore.resetCreateForm();
|
||||
permissionStore.is_edit_form = false;
|
||||
permissionStore.errorsCreateForm = [];
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Card class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ permissionStore.pageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps" class="w-full sm:w-auto">
|
||||
<Link :class="slotProps.class" :href="permissionStore.endpoints.index">
|
||||
<span class="text-sm">{{ permissionStore.indexPageTitle }}</span>
|
||||
<i class="fi fi-rr-list flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<section class="grid w-full grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<div class="lg:col-span-2">
|
||||
<Card>
|
||||
<Form ref="formRef" v-slot="$form" class="flex flex-col gap-4 !w-full">
|
||||
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام سطح</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
permissionStore.errorsCreateForm?.name && !permissionStore.createForm.name ? 'p-invalid' : ''
|
||||
]" v-model="permissionStore.createForm.name" placeholder="نام سطح را وارد کنید" />
|
||||
<Message v-if="permissionStore.errorsCreateForm?.name"
|
||||
severity="error" size="small" variant="simple">{{ permissionStore.errorsCreateForm?.name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="label" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام فارسی سطح</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
permissionStore.errorsCreateForm?.label && !permissionStore.createForm.label ? 'p-invalid' : ''
|
||||
]" v-model="permissionStore.createForm.label" placeholder="نام فارسی سطح را وارد کنید" />
|
||||
<Message v-if="permissionStore.errorsCreateForm?.label" severity="error" size="small" variant="simple">{{ permissionStore.errorsCreateForm?.label[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="parent_id" initialValue="" class="flex flex-col gap-1">
|
||||
<label> والد (مرتبط با) </label>
|
||||
<Select v-model="permissionStore.createForm.parent_id" optionLabel="label" optionValue="id" :pt="{option : '!text-sm'}"
|
||||
:options="permissions.data" filter
|
||||
placeholder="انتخاب سطح" class="w-full">
|
||||
<template #empty>
|
||||
<div class="flex justify-center py-4 items-center h-full">
|
||||
<span class="text-center text-sm">هیچ سطحی پیدا نشده!</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #emptyfilter>
|
||||
<div class="flex justify-center py-4 items-center h-full">
|
||||
<span class="text-center text-sm">هیچ سطحی پیدا نشده!</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #value="slotProps">
|
||||
<div v-if="slotProps.value" class="flex items-center">
|
||||
<div>
|
||||
{{ permissions.data.find(item => item.id === slotProps.value)?.label || 'انتخاب سطح' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</FormField>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-1">
|
||||
<Card>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div class="grid grid-cols-1 justify-between items-center">
|
||||
<span>زمان انتشار:</span>
|
||||
<CustomDatePicker v-model="permissionStore.createForm.datetime" class="mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 justify-start">
|
||||
<Button :loading="permissionStore.loaderCreateForm" @click="permissionStore.update_and_store" type="button" severity="secondary" :label="permissionStore.buttonLabel" class="w-full sm:w-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import { ref, reactive } from 'vue';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import { Link } from "@inertiajs/inertia-vue3";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import {usePermissionStore} from "../../store/Permission.js";
|
||||
const toast = useToast();
|
||||
|
||||
const props = defineProps({
|
||||
permissions: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
level: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const permissions = ref(props.permissions ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
|
||||
const columns = ref(permissionStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? '',
|
||||
level: props.level ?? ''
|
||||
})
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
const onPageChange = (event) => {
|
||||
permissionStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
permissionStore.search(queryParams).then((response) => {
|
||||
searchLoader.value = false;
|
||||
permissions.value = response.props.permissions.data;
|
||||
});
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
permissionStore.exportData({
|
||||
level: queryParams.level,
|
||||
search: queryParams.search
|
||||
});
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
permissionStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
permissionStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ permissionStore.indexPageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="permissionStore.endpoints.create">
|
||||
<span class="text-sm">{{ permissionStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="permissions.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="permissions.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(user => user.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a">
|
||||
<Button
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button severity="info" variant="outlined" asChild v-slot="slotProps">
|
||||
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="permissionStore.endpoints.edit(data.id)">
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,144 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {onMounted , onUnmounted} from 'vue';
|
||||
import {usePetStore} from "../../store/Pet.js";
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import GlobalSelectMedium from "../../components/medium/GlobalSelectMedium.vue";
|
||||
import Card from "../../components/ui/Card.vue";
|
||||
|
||||
const petStore = usePetStore();
|
||||
|
||||
const props = defineProps({
|
||||
is_edit: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
pet: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.is_edit) {
|
||||
petStore.setEditData(props.pet);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
petStore.resetCreateForm();
|
||||
petStore.is_edit_form = false;
|
||||
petStore.errorsCreateForm = [];
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Card class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ petStore.pageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="petStore.endpoints.index">
|
||||
<span class="text-sm">{{ petStore.indexPageTitle }}</span>
|
||||
<i class="fi fi-rr-list flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<section class="grid w-full grid-cols-1 lg:grid-cols-[1fr_22rem] gap-4">
|
||||
<Card>
|
||||
<div class="rounded-2xl bg-white">
|
||||
<Form ref="formRef" v-slot="$form" class=" p-4 grid grid-cols-2 gap-4 !w-full">
|
||||
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام کیس</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
petStore.errorsCreateForm?.name && !petStore.createForm.name ? 'p-invalid' : ''
|
||||
]" v-model="petStore.createForm.name" placeholder="نام کیس خود را وارد کنید" />
|
||||
<Message v-if="petStore.errorsCreateForm?.name"
|
||||
severity="error" size="small" variant="simple">{{ petStore.errorsCreateForm?.name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="email" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نوع کیس</label>
|
||||
<Select class="!text-xs !h-full" placeholder="انتخاب نوع کیس" v-model="petStore.createForm.type" :options="petStore.typeOptions" optionValue="value" optionLabel="label" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
|
||||
<Message v-if="petStore.errorsCreateForm?.type" severity="error" size="small" variant="simple">{{ petStore.errorsCreateForm?.type[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="breed" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نژاد</label>
|
||||
<InputText type="text" class="placeholder:!text-sm" v-model="petStore.createForm.breed" placeholder="نژاد را وارد کنید" />
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="weight" initialValue="" class="flex flex-col gap-1">
|
||||
<label>وزن</label>
|
||||
<InputText type="text" class="placeholder:!text-sm" v-model="petStore.createForm.weight" placeholder="وزن را وارد کنید" />
|
||||
<Message v-if="$field?.invalid" severity="error" size="small" variant="simple">{{ $field.error?.message }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="sterilized" initialValue="" class="flex flex-col gap-1">
|
||||
<label>عقیم سازی</label>
|
||||
<Select class="!text-xs !h-full" placeholder="انتخاب عقیم سازی" v-model="petStore.createForm.sterilized" optionLabel="label" optionValue="value" :options="petStore.sterilizedOptions" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
|
||||
<Message v-if="petStore.errorsCreateForm?.sterilized"
|
||||
severity="error" size="small" variant="simple">{{ petStore.errorsCreateForm?.sterilized[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="gender" initialValue="" class="flex flex-col gap-1">
|
||||
<label>جنسیت</label>
|
||||
<Select class="!text-xs !h-full" placeholder="انتخاب جنسیت" v-model="petStore.createForm.gender" optionLabel="label" optionValue="value" :options="petStore.genderOptions" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
|
||||
<Message v-if="petStore.errorsCreateForm?.gender" severity="error" size="small" variant="simple">{{ petStore.errorsCreateForm?.gender[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="birthday" initialValue="" class="flex flex-col gap-1">
|
||||
<label>تاریخ تولد</label>
|
||||
<CustomDatePicker v-model="petStore.createForm.birthday" class="!text-xs mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
<Message v-if="petStore.errorsCreateForm?.birthday" severity="error" size="small" variant="simple">{{ petStore.errorsCreateForm?.birthday[0] }}</Message>
|
||||
</FormField>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Card>
|
||||
<div class="p-4 rounded-2xl pb-8">
|
||||
<h5>تصویر کاربر</h5>
|
||||
<global-select-medium v-model="petStore.createForm.image"></global-select-medium>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<div class="p-4 grid grid-cols-1 gap-4 rounded-2xl">
|
||||
<div class="grid grid-cols-1 justify-between items-center">
|
||||
<span>زمان انتشار:</span>
|
||||
<CustomDatePicker v-model="petStore.createForm.datetime" class="!text-xs mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 justify-start">
|
||||
<Button :loading="petStore.loaderCreateForm" @click="petStore.update_and_store" type="button" severity="secondary" :label="petStore.buttonLabel" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.p-multiselect {
|
||||
height: auto;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.p-multiselect-label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-header {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-filter {
|
||||
max-height: calc(100px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-item {
|
||||
font-size: 0.875rem;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,273 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import { ref, reactive } from 'vue';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import { Link } from "@inertiajs/inertia-vue3";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { usePetStore } from "../../store/Pet.js";
|
||||
import { useUserStore } from "../../store/User.js";
|
||||
import Button from 'primevue/button';
|
||||
import Tag from 'primevue/tag';
|
||||
const petStore = usePetStore();
|
||||
const userStore = useUserStore();
|
||||
const props = defineProps({
|
||||
pets: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
gender: {
|
||||
type: String
|
||||
},
|
||||
permissions_edit: {
|
||||
type: Boolean
|
||||
},
|
||||
permissions_delete: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
|
||||
const pets = ref(props.pets ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
const filterLoader = ref(false);
|
||||
|
||||
const columns = ref(petStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? '',
|
||||
type: props.type ?? '',
|
||||
gender: props.gender ?? '',
|
||||
})
|
||||
|
||||
const filters = ref({
|
||||
gender: (props.gender && typeof props.gender === 'string') ? props.gender : null,
|
||||
type: petStore.parseStringToArray(props.type),
|
||||
})
|
||||
const confirm = useConfirm();
|
||||
|
||||
const onPageChange = (event) => {
|
||||
petStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
|
||||
const searchParams = {
|
||||
...(queryParams.search && { search: queryParams.search }),
|
||||
...(queryParams.gender && { gender: queryParams.gender }),
|
||||
...petStore.convertFiltersToArrayParams(filters.value)
|
||||
};
|
||||
petStore.search(searchParams);
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
const exportParams = {
|
||||
...(queryParams.search && { search: queryParams.search }),
|
||||
...(queryParams.gender && { gender: queryParams.gender }),
|
||||
...petStore.convertFiltersToArrayParams(filters.value)
|
||||
};
|
||||
petStore.exportData(exportParams);
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const onFilterChange = (newFilters) => {
|
||||
filters.value = { ...newFilters };
|
||||
Object.keys(newFilters).forEach(key => {
|
||||
if (Array.isArray(newFilters[key])) {
|
||||
queryParams[key] = newFilters[key].join(',');
|
||||
} else {
|
||||
queryParams[key] = newFilters[key] || '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyFilters = () => {
|
||||
filterLoader.value = true;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
filters.value = {
|
||||
gender: null,
|
||||
type: [],
|
||||
};
|
||||
queryParams.gender = '';
|
||||
queryParams.type = '';
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
petStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
petStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ petStore.indexPageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="petStore.endpoints.create">
|
||||
<span class="text-sm">{{ petStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="pets.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="pets.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
:filters="filters"
|
||||
:filterLoader="filterLoader"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
@filter-change="onFilterChange"
|
||||
@apply-filters="applyFilters"
|
||||
@clear-filters="clearFilters"
|
||||
>
|
||||
<template #bulk-action="{ selection }" v-if="!permissions_delete">
|
||||
<Button
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(pet => pet.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-sterilized="{ data }">
|
||||
<Tag
|
||||
class="!text-xs"
|
||||
:value="data.sterilized ? 'عقیم شده' : 'عقیم نشده'"
|
||||
:severity="petStore.getSterilized(data.sterilized)"
|
||||
/>
|
||||
</template>
|
||||
<template #column-owner="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<span class="text-right text-sm">{{ data.owner?.fullname }}</span>
|
||||
<Link :href="userStore.endpoints.edit(data.owner.id)" class="text-sm">
|
||||
<i class="fi fi-rr-eye flex !text-xs !text-client-Fontcolor/40"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<Button
|
||||
v-if="!permissions_delete"
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button
|
||||
v-if="!permissions_edit"
|
||||
severity="info"
|
||||
variant="outlined"
|
||||
asChild
|
||||
v-slot="slotProps"
|
||||
>
|
||||
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="petStore.endpoints.edit(data.id)">
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,265 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useReportStore } from "../../store/Report.js";
|
||||
import UIModal from "../../components/ui/UIModal.vue";
|
||||
import CreateReportForm from "../../components/reports/CreateReportForm.vue";
|
||||
const reportStore = useReportStore();
|
||||
const toast = useToast();
|
||||
|
||||
const props = defineProps({
|
||||
reports: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
status: {
|
||||
type: String
|
||||
},
|
||||
})
|
||||
|
||||
const reports = ref(props.reports ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
const editReportData = ref(null);
|
||||
|
||||
const columns = ref(reportStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? '',
|
||||
status: props.status ?? '',
|
||||
})
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
watch(() => props.reports, (newReports) => {
|
||||
if (newReports) {
|
||||
reports.value = newReports;
|
||||
}
|
||||
}, { deep: true, immediate: true });
|
||||
|
||||
|
||||
const onPageChange = (event) => {
|
||||
reportStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
reportStore.search(queryParams).then((response) => {
|
||||
searchLoader.value = false;
|
||||
reports.value = response.props.reports.data;
|
||||
});
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
reportStore.exportData({
|
||||
search: queryParams.search,
|
||||
status: queryParams.status,
|
||||
});
|
||||
}
|
||||
|
||||
const openCreateModal = () => {
|
||||
reportStore.is_edit_form = false;
|
||||
editReportData.value = null;
|
||||
reportStore.showCreateModal = true;
|
||||
}
|
||||
|
||||
const openEditModal = (reportData) => {
|
||||
reportStore.is_edit_form = true;
|
||||
editReportData.value = reportData;
|
||||
reportStore.showCreateModal = true;
|
||||
}
|
||||
|
||||
const closeCreateModal = () => {
|
||||
reportStore.showCreateModal = false;
|
||||
reportStore.is_edit_form = false;
|
||||
editReportData.value = null;
|
||||
}
|
||||
|
||||
const handleCreateOrUpdateReport = async (reportId = null) => {
|
||||
const formData = reportStore.createForm;
|
||||
|
||||
if (reportStore.is_edit_form && reportId) {
|
||||
await reportStore.update(reportId, formData);
|
||||
} else {
|
||||
await reportStore.create(formData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
reportStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
reportStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ reportStore.indexPageTitle }}</h2>
|
||||
<Button
|
||||
severity="secondary"
|
||||
@click="openCreateModal"
|
||||
>
|
||||
<span class="text-sm">{{ reportStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="reports.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="reports.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(report => report.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-status="{ data }">
|
||||
<Tag
|
||||
class="!text-xs"
|
||||
:value="data.status"
|
||||
:severity="reportStore.getStatusSeverity(data.status)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button
|
||||
severity="info"
|
||||
variant="outlined"
|
||||
class="!w-8 !h-8"
|
||||
@click="openEditModal(data)"
|
||||
>
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
|
||||
<UIModal
|
||||
v-model:visible="reportStore.showCreateModal"
|
||||
:title="reportStore.is_edit_form ? 'ویرایش گزارش' : 'ایجاد گزارش جدید'"
|
||||
size="medium"
|
||||
maximizable
|
||||
@hide="closeCreateModal"
|
||||
>
|
||||
<template #default="{ isFullscreen }">
|
||||
<CreateReportForm
|
||||
:reportData="editReportData"
|
||||
:isEdit="reportStore.is_edit_form"
|
||||
:isFullscreen="isFullscreen"
|
||||
@submit="handleCreateOrUpdateReport"
|
||||
@cancel="closeCreateModal"
|
||||
/>
|
||||
</template>
|
||||
</UIModal>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,134 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {ref , onMounted , onUnmounted} from 'vue';
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import {useRoleStore} from "../../store/Role.js";
|
||||
import Card from "../../components/ui/Card.vue";
|
||||
|
||||
|
||||
const roleStore = useRoleStore();
|
||||
const props = defineProps({
|
||||
is_edit : {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
role: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.is_edit) {
|
||||
roleStore.setEditData(props.role);
|
||||
roleStore.createForm.selectedPermissionIds = props.role.permissions;
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
roleStore.resetCreateForm();
|
||||
roleStore.is_edit_form = false;
|
||||
roleStore.errorsCreateForm = [];
|
||||
})
|
||||
|
||||
const managerSelected = () => {
|
||||
const all = {};
|
||||
props.permissions.forEach(permission => {
|
||||
const filtered = permission.permissions_parent
|
||||
.filter(p => !p.name.includes('own'))
|
||||
.map(p => p.id);
|
||||
|
||||
all[permission.id] = filtered;
|
||||
});
|
||||
roleStore.createForm.selectedPermissionIds = all;
|
||||
}
|
||||
const selectAllWithOwn = () => {
|
||||
const all = {};
|
||||
props.permissions.forEach(permission => {
|
||||
all[permission.id] = permission.permissions_parent.map(p => p.id);
|
||||
});
|
||||
roleStore.createForm.selectedPermissionIds = all;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Card class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ roleStore.pageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps" class="w-full sm:w-auto">
|
||||
<Link :class="slotProps.class" :href="roleStore.endpoints.index">
|
||||
<span class="text-sm">{{ roleStore.indexPageTitle }}</span>
|
||||
<i class="fi fi-rr-list flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<section class="grid w-full grid-cols-1 lg:grid-cols-[1fr_26rem] gap-4">
|
||||
<Card>
|
||||
<div class="">
|
||||
<Form ref="formRef" v-slot="$form" class="bg-white p-4 rounded-2xl flex flex-col gap-4 !w-full">
|
||||
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام مقام</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
roleStore.errorsCreateForm?.name && !roleStore.createForm.name ? 'p-invalid' : ''
|
||||
]" v-model="roleStore.createForm.name" placeholder="نام مقام را وارد کنید" />
|
||||
<Message v-if="roleStore.errorsCreateForm?.name"
|
||||
severity="error" size="small" variant="simple">{{ roleStore.errorsCreateForm?.name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="label" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام فارسی مقام</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
roleStore.errorsCreateForm?.label && !roleStore.createForm.label ? 'p-invalid' : ''
|
||||
]" v-model="roleStore.createForm.label" placeholder="نام فارسی مقام را وارد کنید" />
|
||||
<Message v-if="roleStore.errorsCreateForm?.label" severity="error" size="small" variant="simple">{{ roleStore.errorsCreateForm?.label[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="permissions_parent" initialValue="" class="flex flex-col gap-1">
|
||||
<label>انتخاب سطح دسترسی برای این مقام</label>
|
||||
<div class="grid grid-cols-3 max-lg:grid-cols-2 mt-1 gap-4 max-sm:grid-cols-1">
|
||||
<div v-for="(permission , index) in permissions" :key="permission.id" class="grid grid-cols-1 mt-1">
|
||||
<span class="mb-1 text-sm">{{permission.label}}</span>
|
||||
<select v-model="roleStore.createForm.selectedPermissionIds[permission.id]" multiple>
|
||||
<option v-for="(p , index) in permission.permissions_parent" :key="p.id" :value="p.id">{{p.label}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</FormField>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div class="rounded-3xl lg:col-span-1 z-20">
|
||||
<div class="p-4 bg-white grid grid-cols-1 gap-4 rounded-2xl">
|
||||
<div class="grid grid-cols-1 justify-between items-center">
|
||||
<span>زمان انتشار:</span>
|
||||
<CustomDatePicker v-model="roleStore.createForm.datetime" class="!text-xs mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-3 mt-1 justify-between items-center">
|
||||
<span class="col-span-2 mb-2">عملیات سریع:</span>
|
||||
<Button @click="managerSelected" icon="fi fi-rr-choose" :pt="{icon: 'flex'}" size="small" outlined class="!text-xs h-10" label="انتخاب بعنوان مدیر"/>
|
||||
<Button @click="selectAllWithOwn" icon="fi fi-rr-choose" :pt="{icon: 'flex'}" size="small" outlined class="!text-xs h-10 truncate" label="انتخاب با محدودیت کاربری"/>
|
||||
<div class="col-span-2 py-4">
|
||||
<h3 class="text-xs font-black">انتخاب بعنوان مدیر:</h3>
|
||||
<p class="leading-7 font-light text-xs">این گذینه تمامی سطح های مورد نظر <span class="font-bold">به جز سطح محدودیت کاربر</span> رو بصورت خودکار انتخاب کرده و تنها کافی است که نام و نام فارسی مقام را مشخص و ثبت کنید.</p>
|
||||
<h3 class="text-xs font-black mt-2">انتخاب با محدودیت کاربری:</h3>
|
||||
<p class="leading-7 text-xs font-light">این گذینه تمامی سطح های مورد نظر رو بصورت خودکار انتخاب کرده در هر بخش <span class="font-bold"> سطح محدودیت کاربر هم اعمال خواهد</span> شد تنها کافی است که نام و نام فارسی مقام را مشخص و ثبت کنید. </p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 justify-start">
|
||||
<Button :loading="roleStore.loaderCreateForm" @click="roleStore.update_and_store" type="button" severity="secondary" :label="roleStore.buttonLabel" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {ref, reactive} from 'vue';
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import {useConfirm} from 'primevue/useconfirm';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import {useRoleStore} from "../../store/Role.js";
|
||||
|
||||
const props = defineProps({
|
||||
roles: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
})
|
||||
const roleStore = useRoleStore();
|
||||
|
||||
const roles = ref(props.roles ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
|
||||
const columns = ref(roleStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? ''
|
||||
})
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
const onPageChange = (event) => {
|
||||
roleStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
roleStore.search(queryParams).then((response) => {
|
||||
searchLoader.value = false;
|
||||
roles.value = response.props.roles.data;
|
||||
});
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
roleStore.exportData({
|
||||
search: queryParams.search
|
||||
});
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
roleStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
roleStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ roleStore.indexPageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="roleStore.endpoints.create">
|
||||
<span class="text-sm">{{ roleStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] overflow-hidden border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="roles.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="roles.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(user => user.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a">
|
||||
<Button
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button severity="info" variant="outlined" asChild v-slot="slotProps">
|
||||
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="roleStore.endpoints.edit(data.id)">
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,138 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {useToast} from "primevue";
|
||||
import Master from "../../../layouts/master.vue";
|
||||
import {useGlobalStore} from "../../../store/Global.js";
|
||||
|
||||
const src = ref(null);
|
||||
const file = ref({});
|
||||
const loading = ref(false);
|
||||
const isUpload = ref(false);
|
||||
const loadingUpload = ref(false);
|
||||
|
||||
const toast = useToast();
|
||||
const globalStore = useGlobalStore();
|
||||
const user_form = ref({
|
||||
name: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
user_form.value = {
|
||||
name: globalStore.current_user?.name ?? '',
|
||||
first_name: globalStore.current_user?.first_name ?? '',
|
||||
last_name: globalStore.current_user?.last_name ?? '',
|
||||
};
|
||||
})
|
||||
const errors = ref([]);
|
||||
const onFileSelect = (event) => {
|
||||
isUpload.value = false;
|
||||
file.value = event.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = async (e) => {
|
||||
src.value = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file.value);
|
||||
}
|
||||
|
||||
const onRemoveTemplatingFile = () => {
|
||||
src.value = null;
|
||||
file.value = {};
|
||||
};
|
||||
|
||||
const onFormSubmit = () => {
|
||||
loading.value = true;
|
||||
axios.post(`/admin/setting-profile/update-data/${globalStore.current_user.id}` , user_form.value ).then(res => {
|
||||
toast.add({ severity: 'success', summary: 'اطلاعات شما با موفقیت ثبت شد.', life: 3000 });
|
||||
errors.value = [];
|
||||
loading.value = false;
|
||||
globalStore.current_user.name = res.data.name;
|
||||
globalStore.current_user.first_name = res.data.first_name;
|
||||
globalStore.current_user.last_name = res.data.last_name;
|
||||
}).catch(error => {
|
||||
errors.value = error.response.data.errors;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const uploadProfile = () => {
|
||||
loadingUpload.value = true;
|
||||
const form = new FormData();
|
||||
form.append('file' , file.value );
|
||||
axios.post('/admin/setting-profile/update-avatar' , form ).then(res => {
|
||||
toast.add({ severity: 'success', summary: 'پروفایل شما با موفقیت آپلود شد', life: 3000 });
|
||||
isUpload.value = true;
|
||||
loadingUpload.value = false;
|
||||
globalStore.current_user.profile_url = res.data.profile_url ?? '';
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<section class="grid grid-cols-[1fr] pb-10 gap-4 px-10 max-lg:px-4">
|
||||
<div class="!w-1/2 max-lg:!w-full !m-auto grid grid-cols-a text-client-Fontcolor justify-center text-center">
|
||||
<i class="fi fi-rr-user-pen flex text-5xl m-auto"></i>
|
||||
<h4 class="font-extrabold text-2xl mt-4">ویرایش اطلاعات</h4>
|
||||
<p class="leading-6 text-sm font-light mt-2">برای تغییر آواتار خود، تصویر مورد نظر را از دستگاه خود انتخاب کرده و بارگذاری کنید. پس از بارگذاری، آواتار جدید شما بهطور خودکار در پروفایل بهروز میشود.</p>
|
||||
</div>
|
||||
<div class="card grid grid-cols-1 !w-[30rem] max-lg:!w-full !m-auto justify-center">
|
||||
<div class="card flex flex-col items-center gap-6 !mb-8">
|
||||
<FileUpload mode="basic" @select="onFileSelect" customUpload auto severity="secondary" chooseLabel="انتخاب آواتار" class="p-button-outlined flex flex-row-reverse !text-xs !text-white !bg-client-primary" />
|
||||
<img v-if="!src && globalStore.current_user.profile_url" :src="globalStore.current_user.profile_url" alt="Image" class="shadow-md rounded-xl w-auto max-h-28 object-contain" />
|
||||
<img v-if="src" :src="src" alt="Image" class="shadow-md rounded-xl w-auto max-h-28 object-contain" :style="!isUpload ? {filter: 'grayscale(100%)' } : {}" />
|
||||
<div v-if="!isUpload" class="grid grid-cols-2a gap-2 -mt-3 items-center justify-center">
|
||||
<Button v-if="src" icon="fi fi-rr-trash" :pt="{icon: 'flex'}" @click="onRemoveTemplatingFile()" outlined severity="danger" />
|
||||
<Button v-if="src" icon="fi fi-rr-cloud-upload" :pt="{icon: 'flex text-sm'}" label=" آپلود آواتار"
|
||||
class="!text-xs flex flex-row-reverse !py-3 !font-light"
|
||||
@click="uploadProfile()" :loading="loadingUpload" outlined severity="secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<Fieldset legend="ویرایش حساب شما" pt:legend="!text-sm !rounded-lg !border-[1px] !border-gray-200"
|
||||
pt:root:class="!border-[1px] !rounded-xl"
|
||||
pt:content:class="grid grid-cols-1 !p-6 !pt-4 flex justify-center">
|
||||
<Form @submit="onFormSubmit" class="grid grid-cols-1 gap-4 w-full !border-0 !text-sm">
|
||||
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام کاربری</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm !text-sm',
|
||||
errors?.name && !errors.name ? 'p-invalid' : ''
|
||||
]" v-model="user_form.name" placeholder="نام کاربری را وارد کنید" />
|
||||
<Message v-if="errors?.name"
|
||||
severity="error" size="small" variant="simple">{{ errors?.name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="first_name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام کوچک</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm !text-sm',
|
||||
errors?.first_name && !errors.first_name ? 'p-invalid' : ''
|
||||
]" v-model="user_form.first_name" placeholder="نام کوچک خود را وارد کنید" />
|
||||
<Message v-if="errors?.first_name"
|
||||
severity="error" size="small" variant="simple">{{ errors?.first_name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="last_name" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام خانوادگی</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm !text-sm',
|
||||
errors?.last_name && !errors.last_name ? 'p-invalid' : ''
|
||||
]" v-model="user_form.last_name" placeholder="نام کوچک خود را وارد کنید" />
|
||||
<Message v-if="errors?.last_name"
|
||||
severity="error" size="small" variant="simple">{{ errors?.last_name[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" asChild name="email" :initialValue="globalStore.current_user.email">
|
||||
<section class="flex flex-col gap-2">
|
||||
<InputText type="email" class="flex pt-2 !text-sm !font-light" disabled id="email" fluid />
|
||||
</section>
|
||||
</FormField>
|
||||
<Button type="submit" :loading="loading" severity="secondary" label="ذخیره اطلاعات" />
|
||||
</Form>
|
||||
</Fieldset>
|
||||
</div>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import {reactive, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import Master from "../../../layouts/master.vue";
|
||||
|
||||
interface PasswordInterface {
|
||||
password: string,
|
||||
newPassword: string,
|
||||
newPassword_confirmation: string,
|
||||
}
|
||||
|
||||
|
||||
const toast = useToast();
|
||||
const loader = ref<boolean>(false);
|
||||
const errors = ref<any>([]);
|
||||
const ChangePasswordForm = reactive<PasswordInterface>({
|
||||
password : '',
|
||||
newPassword : '',
|
||||
newPassword_confirmation : '',
|
||||
});
|
||||
|
||||
|
||||
const ChangePasswordHandler = () => {
|
||||
loader.value = true;
|
||||
axios.post('/admin/setting-profile/change-password' , ChangePasswordForm).then(res => {
|
||||
if (res.status == 200){
|
||||
toast.add({severity : 'success' , summary : res.data.message});
|
||||
ChangePasswordForm.password = '';
|
||||
ChangePasswordForm.newPassword = '';
|
||||
ChangePasswordForm.newPassword_confirmation = '';
|
||||
}
|
||||
loader.value = false;
|
||||
errors.value = [];
|
||||
}).catch(error => {
|
||||
errors.value = error.response.data.errors ?? [];
|
||||
if (error.status == 401 && error.response.data.message){
|
||||
toast.add({severity : 'error' , summary : error.response.data.message})
|
||||
}
|
||||
loader.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Panel toggleable class="w-full !rounded-2xl bg-client-gray/10">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fi fi-rr-lock"></i>
|
||||
<span class="font-semibold text-sm">تغییر کلمه عبور</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid grid-cols-1 gap-4 content-start">
|
||||
<p class="mb-4 leading-7 text-sm !font-extralight">برای افزایش امنیت حساب کاربری خود، توصیه میشود که کلمه عبور خود را به طور دورهای تغییر دهید. برای این کار، ابتدا کلمه عبور فعلی خود را وارد کنید، سپس کلمه عبور جدید را وارد کرده و آن را دوباره تایید کنید. توجه داشته باشید که کلمه عبور جدید باید حداقل 8 کاراکتر باشد و شامل ترکیبی از حروف، اعداد و نمادها باشد.</p>
|
||||
<div class="m-auto">
|
||||
<FloatLabel variant="on" class=" m-auto">
|
||||
<Password id="password" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask
|
||||
v-model="ChangePasswordForm.password"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/20 !text-client-Fontcolor"/>
|
||||
<label for="on_label" class="!text-client-Fontcolor !bg-white !text-sm font-light">کلمه عبور فعلی</label>
|
||||
</FloatLabel>
|
||||
<Message
|
||||
v-if="errors.password"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{errors.password[0]}}</Message>
|
||||
</div>
|
||||
<div class="m-auto">
|
||||
<FloatLabel variant="on" class="m-auto">
|
||||
<Password id="password" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask
|
||||
v-model="ChangePasswordForm.newPassword"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/20 !text-client-Fontcolor"/>
|
||||
<label for="on_label" class="!text-client-Fontcolor !bg-white !text-sm font-light"> کلمه عبور جدید</label>
|
||||
</FloatLabel>
|
||||
<Message
|
||||
v-if="errors.newPassword"
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small">{{errors.newPassword[0]}}</Message>
|
||||
</div>
|
||||
<div class="m-auto">
|
||||
<FloatLabel variant="on" class="m-auto">
|
||||
<Password id="password" type="password" :feedback="false"
|
||||
appendTo="self" toggleMask
|
||||
v-model="ChangePasswordForm.newPassword_confirmation"
|
||||
class="placeholder:!font-light !text-sm !bg-white h-12 !border-gray-500/20 !text-client-Fontcolor"/>
|
||||
<label for="on_label" class="!text-client-Fontcolor !bg-white !text-sm font-light">تکرار کلمه عبور جدید</label>
|
||||
</FloatLabel>
|
||||
<Message
|
||||
class="h-7 mt-1 !text-xs px-1"
|
||||
variant="simple"
|
||||
severity="error" size="small"></Message>
|
||||
</div>
|
||||
<Button @click="ChangePasswordHandler" :loading="loader" label=" تغییر کلمه عبور" severity="secondary"
|
||||
class="m-auto flex flex-row-reverse items-center !font-normal" />
|
||||
</div>
|
||||
</Panel>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,149 @@
|
||||
<script setup>
|
||||
import Master from "../../../layouts/master.vue";
|
||||
import { ref, onMounted } from 'vue';
|
||||
import TableGeneric from "../../../components/TableGeneric.vue";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useSessionStore } from "../../../store/Session.js";
|
||||
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
|
||||
const props = defineProps({
|
||||
sessions: {
|
||||
type: Object,
|
||||
default: () => ({ data: [], meta: {} })
|
||||
}
|
||||
});
|
||||
|
||||
const sessionStore = useSessionStore();
|
||||
const selectedItems = ref([]);
|
||||
|
||||
const columns = ref(sessionStore.columns);
|
||||
|
||||
onMounted(() => {
|
||||
sessionStore.setSessions(props.sessions || []);
|
||||
});
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
sessionStore.setSelectedSessions(selection);
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
sessionStore.deleteSession(data.id)
|
||||
.then((response) => {
|
||||
toast.add({ severity: 'success', summary: response.data.message });
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.add({ severity: 'error', summary: error.response.data.message });
|
||||
});
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
sessionStore.bulkDeleteSessions(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ sessionStore.indexPageTitle }}</h2>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="sessionStore.sessions"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="false"
|
||||
:searchable="true"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
label="حذف همه نشست ها"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
:loading="sessionStore.bulkLoader"
|
||||
@click="() => confirmDeleteSelected(selection.map(item => item.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-1a">
|
||||
<Button
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
:loading="sessionStore.loader"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,170 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import {onMounted , onUnmounted} from 'vue';
|
||||
import {useUserStore} from "../../store/User.js";
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
import GlobalSelectMedium from "../../components/medium/GlobalSelectMedium.vue";
|
||||
import Card from "../../components/ui/Card.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const props = defineProps({
|
||||
is_edit: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
roles: {
|
||||
type: Object,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.is_edit) {
|
||||
userStore.setEditData(props.user);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
userStore.resetCreateForm();
|
||||
userStore.is_edit_form = false;
|
||||
userStore.errorsCreateForm = [];
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<Card class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ userStore.pageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="userStore.endpoints.index">
|
||||
<span class="text-sm">{{ userStore.indexPageTitle }}</span>
|
||||
<i class="fi fi-rr-list flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<section class="grid w-full grid-cols-1 lg:grid-cols-[1fr_22rem] gap-4">
|
||||
<Card>
|
||||
<div class="rounded-2xl bg-white">
|
||||
<Form ref="formRef" v-slot="$form" class=" p-4 flex flex-col gap-4 !w-full">
|
||||
<FormField v-slot="$field" name="username" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام کاربری</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
userStore.errorsCreateForm?.username && !userStore.createForm.username ? 'p-invalid' : ''
|
||||
]" v-model="userStore.createForm.username" placeholder="نام کاربری خود را وارد کنید" />
|
||||
<Message v-if="userStore.errorsCreateForm?.username"
|
||||
severity="error" size="small" variant="simple">{{ userStore.errorsCreateForm?.username[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="email" initialValue="" class="flex flex-col gap-1">
|
||||
<label>ایمیل کاربری</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
userStore.errorsCreateForm?.email && !userStore.createForm.email ? 'p-invalid' : ''
|
||||
]" v-model="userStore.createForm.email" placeholder="ایمیل خود را وارد کنید" />
|
||||
<Message v-if="userStore.errorsCreateForm?.email" severity="error" size="small" variant="simple">{{ userStore.errorsCreateForm?.email[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="national_code" initialValue="" class="flex flex-col gap-1">
|
||||
<label>کد ملی</label>
|
||||
<InputText type="text" :class="[
|
||||
'placeholder:!text-sm',
|
||||
userStore.errorsCreateForm?.national_code && !userStore.createForm.national_code ? 'p-invalid' : ''
|
||||
]" v-model="userStore.createForm.national_code" placeholder="کد ملی خود را وارد کنید" />
|
||||
<Message v-if="userStore.errorsCreateForm?.national_code" severity="error" size="small" variant="simple">{{ userStore.errorsCreateForm?.national_code[0] }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="phone" initialValue="" class="flex flex-col gap-1">
|
||||
<label>شماره تماس</label>
|
||||
<InputText type="text" class="placeholder:!text-sm" v-model="userStore.createForm.phone" placeholder="شماره تماس خود را وارد کنید" />
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="fullname" initialValue="" class="flex flex-col gap-1">
|
||||
<label>نام خانوادگی</label>
|
||||
<InputText type="text" class="placeholder:!text-sm" v-model="userStore.createForm.fullname" placeholder="نام خانوادگی خود را وارد کنید" />
|
||||
<Message v-if="$field?.invalid" severity="error" size="small" variant="simple">{{ $field.error?.message }}</Message>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="password" initialValue="" class="flex flex-col gap-1">
|
||||
<label>کلمه عبور</label>
|
||||
<Password type="text" :inputClass="['placeholder:!text-sm' , { 'p-invalid' : userStore.errorsCreateForm?.password && !userStore.createForm.password}]"
|
||||
v-model="userStore.createForm.password" placeholder="کلمه عبور خود را وارد کنید" :feedback="false" toggleMask fluid />
|
||||
<Message v-if="userStore.errorsCreateForm?.password"
|
||||
severity="error" size="small" variant="simple">{{ userStore.errorsCreateForm?.password[0] }}</Message>
|
||||
<p class="text-client-Fontcolor/60 text-xs font-normal">در صورتی که کلمه عبور را نمیخواهید تغییر دهید خالی بگذارید</p>
|
||||
</FormField>
|
||||
<FormField v-slot="$field" name="password_confirmation" initialValue="" class="flex flex-col gap-1">
|
||||
<label>تکرار کلمه عبور</label>
|
||||
<Password type="text" inputClass="placeholder:!text-sm" v-model="userStore.createForm.password_confirmation" placeholder="تکرار کلمه عبور خود را وارد کنید" :feedback="false" toggleMask fluid />
|
||||
<Message v-if="$field?.invalid" severity="error" size="small" variant="simple">{{ $field.error?.message }}</Message>
|
||||
</FormField>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Card>
|
||||
<div class="p-4 rounded-2xl pb-8">
|
||||
<h5>تصویر کاربر</h5>
|
||||
<global-select-medium v-model="userStore.createForm.image"></global-select-medium>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="mt-4">
|
||||
<div class="mt-4 p-4 grid grid-cols-1 gap-4 rounded-2xl">
|
||||
<div class="grid grid-cols-1 justify-between items-center">
|
||||
<span>زمان انتشار:</span>
|
||||
<CustomDatePicker v-model="userStore.createForm.datetime" class="!text-xs mt-2" type="datetime" compact-time></CustomDatePicker>
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-between items-center">
|
||||
<span class="flex">سطح:</span>
|
||||
<SelectButton class="!text-xs" v-model="userStore.createForm.level" :options="userStore.levelOptions" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-between items-center">
|
||||
<span class="flex">مقام:</span>
|
||||
<MultiSelect class="!text-xs"
|
||||
v-model="userStore.createForm.role"
|
||||
:pt="{ option : '!text-sm' }"
|
||||
placeholder="انتخاب مقام"
|
||||
optionLabel="label"
|
||||
optionValue="id"
|
||||
:options="roles" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 justify-start">
|
||||
<Button :loading="userStore.loaderCreateForm" @click="userStore.update_and_store" type="button" severity="secondary" :label="userStore.buttonLabel" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.p-multiselect {
|
||||
height: auto;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.p-multiselect-label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-header {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-filter {
|
||||
max-height: calc(100px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.p-multiselect .p-multiselect-item {
|
||||
font-size: 0.875rem;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import Master from "../../layouts/master.vue";
|
||||
import { ref, reactive } from 'vue';
|
||||
import TableGeneric from "../../components/TableGeneric.vue";
|
||||
import { Link } from "@inertiajs/inertia-vue3";
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useUserStore } from "../../store/User.js";
|
||||
const userStore = useUserStore();
|
||||
const toast = useToast();
|
||||
|
||||
const props = defineProps({
|
||||
users: {
|
||||
type: Object,
|
||||
},
|
||||
search: {
|
||||
type: String
|
||||
},
|
||||
level: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const users = ref(props.users ?? []);
|
||||
const selectedItems = ref([]);
|
||||
const searchLoader = ref(false);
|
||||
|
||||
const columns = ref(userStore.columns)
|
||||
|
||||
const queryParams = reactive({
|
||||
search: props.search ?? '',
|
||||
level: props.level ?? ''
|
||||
})
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
const onPageChange = (event) => {
|
||||
userStore.navigateToPage(event.page + 1);
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
searchLoader.value = true;
|
||||
userStore.search(queryParams).then((response) => {
|
||||
searchLoader.value = false;
|
||||
users.value = response.props.users.data;
|
||||
});
|
||||
}
|
||||
|
||||
const exportData = () => {
|
||||
userStore.exportData({
|
||||
level: queryParams.level,
|
||||
search: queryParams.search
|
||||
});
|
||||
}
|
||||
|
||||
const onSelectionChange = (selection) => {
|
||||
selectedItems.value = selection;
|
||||
};
|
||||
|
||||
const onSearchChange = (value) => {
|
||||
queryParams.search = value;
|
||||
searchData();
|
||||
};
|
||||
|
||||
const showDeleteConfirm = (data) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
userStore.deleteRecord(data.id);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const confirmDeleteSelected = (ids) => {
|
||||
confirm.require({
|
||||
group: 'deleteTemplate',
|
||||
header: 'مطمعن هستید؟',
|
||||
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
|
||||
icon: 'fi fi-rr-info',
|
||||
rejectProps: {
|
||||
label: 'انصراف',
|
||||
icon: 'fi fi-rr-ban',
|
||||
outlined: true,
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'حذف',
|
||||
icon: 'fi fi-rr-check',
|
||||
size: 'small',
|
||||
class: '!text-xs !flex pt-1 !item-center',
|
||||
iconClass: '!flex !text-xs',
|
||||
},
|
||||
accept: () => {
|
||||
userStore.bulkDelete(ids);
|
||||
},
|
||||
reject: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
|
||||
<h2 class="font-bold text-client-Fontcolor">{{ userStore.indexPageTitle }}</h2>
|
||||
<Button severity="secondary" asChild v-slot="slotProps">
|
||||
<Link :class="slotProps.class" :href="userStore.endpoints.create">
|
||||
<span class="text-sm">{{ userStore.pageTitle }}</span>
|
||||
<i class="fi fi-rr-plus flex text-sm"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
|
||||
<TableGeneric
|
||||
:data="users.data"
|
||||
:columns="columns"
|
||||
:selectable="true"
|
||||
:pagination="users.meta"
|
||||
:searchable="true"
|
||||
:searchValue="queryParams.search"
|
||||
v-model="selectedItems"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-change="onPageChange"
|
||||
@search-change="onSearchChange"
|
||||
>
|
||||
<template #bulk-action="{ selection }">
|
||||
<Button
|
||||
label="حذف همه"
|
||||
size="small"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-trash"
|
||||
severity="danger"
|
||||
class="!text-xs !flex"
|
||||
outlined
|
||||
@click="() => confirmDeleteSelected(selection.map(user => user.id))"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #toolbar-actions>
|
||||
<div class="grid grid-cols-a gap-4 justify-start">
|
||||
<Button
|
||||
label="خروجی اکسل"
|
||||
:pt="{icon : 'flex'}"
|
||||
icon="fi fi-rr-file-export"
|
||||
class="!text-xs !bg-client-primary"
|
||||
@click="exportData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #column-actions="{ data }">
|
||||
<div class="grid grid-cols-2a justify-start gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
class="!w-8 !h-8"
|
||||
variant="outlined"
|
||||
severity="danger"
|
||||
icon="fi fi-rr-trash"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"
|
||||
@click="showDeleteConfirm(data)"
|
||||
/>
|
||||
<Button severity="info" variant="outlined" asChild v-slot="slotProps">
|
||||
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="userStore.endpoints.edit(data.id)">
|
||||
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
import Master from "../layouts/master.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<master>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
Radically dashboard
|
||||
</div>
|
||||
</master>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useGlobalStore } from '../store/Global';
|
||||
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
const toggleSidebar = () => {
|
||||
globalStore.sidebarOpen = !globalStore.sidebarOpen;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="h-16 !bg-white grid items-center shadow-md
|
||||
shadow-client-gray/10 px-4 grid-cols-2a justify-between relative z-20">
|
||||
<div class="grid grid-cols-2a justify-start items-center gap-4">
|
||||
<Button
|
||||
icon="fi fi-rr-menu-burger"
|
||||
class="lg:hidden !w-8 !h-8 visible sm:invisible flex items-center"
|
||||
pt:icon="mt-[.6rem]"
|
||||
variant="outlined"
|
||||
@click="toggleSidebar"
|
||||
/>
|
||||
<figure class="max-sm:w-auto w-auto !h-12 flex justify-center p-1">
|
||||
<img class="h-full w-full object-contain m-auto" src="@/images/radicall_logo.png" alt="">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-start items-center gap-4">
|
||||
<Avatar icon="fi fi-rr-user" severity="secondary" class="mr-2 !w-10 !h-10 flex" shape="circle" />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import {Link} from "@inertiajs/inertia-vue3";
|
||||
const props = defineProps([
|
||||
'currentPage',
|
||||
'lastPage',
|
||||
'total',
|
||||
'perPage',
|
||||
'links',
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="pagination" v-if="lastPage > 1">
|
||||
<template v-for="(link , index) in links" :key="index">
|
||||
<div class="paginate" v-if="link.label == 'قبلی'">
|
||||
<Link v-if="link.url" :href="link.url">
|
||||
<i class="fi fi-rr-angle-small-right flex text-xl"></i>
|
||||
</Link>
|
||||
</div>
|
||||
<div class="paginate" v-else-if="link.label == 'بعدی'">
|
||||
<Link v-if="link.url" :href="link.url">
|
||||
<i class="fi fi-rr-angle-small-left flex text-xl"></i>
|
||||
</Link>
|
||||
</div>
|
||||
<div class="paginate flex items-center" :class="{'!text-white !bg-client-primary' : currentPage == link.label }" v-else>
|
||||
<Link v-if="link.url" :href="link.url" class="flex pt-1">{{link.label}}</Link>
|
||||
<Link v-else href="" class="flex">{{link.label}}</Link>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
section.pagination{
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
section.pagination div.paginate:first-child a{
|
||||
border-top-right-radius: .4rem;
|
||||
border-bottom-right-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate:last-child a{
|
||||
border-top-left-radius: .4rem;
|
||||
border-bottom-left-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate a{
|
||||
display: flex;
|
||||
height: 2.2rem;
|
||||
width: 2.2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
@apply text-client-Fontcolor
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
currentPage: Number,
|
||||
lastPage: Number,
|
||||
total: Number,
|
||||
perPage: Number,
|
||||
links: Array,
|
||||
topScroll: String,
|
||||
onPaginate: { type: Function, required: true }
|
||||
});
|
||||
|
||||
const paginateHandler = (page) => {
|
||||
console.log(props.topScroll)
|
||||
document.getElementById(props.topScroll).scrollIntoView({behavior: "smooth"});
|
||||
props.onPaginate(page);
|
||||
}
|
||||
|
||||
const perv = () => {
|
||||
if (props.currentPage > 1) {
|
||||
// document.getElementById(props.topScroll).scrollIntoView({behavior: "smooth"});
|
||||
props.onPaginate(props.currentPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
if (props.currentPage < props.lastPage) {
|
||||
// document.getElementById(props.topScroll).scrollIntoView({behavior: "smooth"});
|
||||
props.onPaginate(props.currentPage + 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="pagination" v-if="lastPage > 1">
|
||||
<template v-for="(link , index) in links" :key="index">
|
||||
<div class="paginate cursor-pointer" v-if="link.label == 'قبلی'">
|
||||
<div @click="perv" v-if="link.url">
|
||||
<i class="fi fi-rr-angle-small-right flex text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paginate cursor-pointer" v-else-if="link.label == 'بعدی'">
|
||||
<div @click="next" v-if="link.url">
|
||||
<i class="fi fi-rr-angle-small-left flex text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paginate cursor-pointer flex items-center" :class="{'!text-white !bg-client-primary' : currentPage == link.label }" v-else>
|
||||
<div @click="paginateHandler(link.label)" v-if="link.url" :href="link.url" class="flex pt-1">{{link.label}}</div>
|
||||
<div v-else href="" class="flex">{{link.label}}</div>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
section.pagination{
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
section.pagination div.paginate:first-child div{
|
||||
border-top-right-radius: .4rem;
|
||||
border-bottom-right-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate:last-child div{
|
||||
border-top-left-radius: .4rem;
|
||||
border-bottom-left-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate div{
|
||||
display: flex;
|
||||
height: 2.2rem;
|
||||
width: 2.2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
@apply text-client-Fontcolor
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted, watch} from 'vue';
|
||||
import {Link, usePage} from '@inertiajs/inertia-vue3';
|
||||
import type { Ref } from 'vue';
|
||||
import type { MenuItem } from 'primevue/menuitem';
|
||||
import {useGlobalStore} from '../store/Global';
|
||||
|
||||
interface SidebarItem extends MenuItem {
|
||||
label: string;
|
||||
icon?: string;
|
||||
to?: string;
|
||||
expanded?: boolean;
|
||||
items?: SidebarItem[];
|
||||
permission?: string | null;
|
||||
}
|
||||
|
||||
const globalStore = useGlobalStore();
|
||||
const {url ,props} = usePage();
|
||||
const currentUser = ref(props.value.auth.User);
|
||||
|
||||
const userPermissions = computed(() => props.value.auth.permissions ?? []);
|
||||
|
||||
function hasPermission(permission) {
|
||||
if (!permission) return true;
|
||||
return userPermissions.value.includes(permission);
|
||||
}
|
||||
|
||||
function filterSidebarItems(items) {
|
||||
return items
|
||||
.filter(item => hasPermission(item.permission))
|
||||
.map(item => {
|
||||
if (item.items) {
|
||||
const filteredChildren = filterSidebarItems(item.items);
|
||||
if (filteredChildren.length > 0) {
|
||||
return { ...item, items: filteredChildren };
|
||||
} else if (!item.permission) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const filteredItems = computed(() => filterSidebarItems(items.value));
|
||||
|
||||
// @ts-ignore
|
||||
const items: Ref<SidebarItem[]> = ref([
|
||||
{
|
||||
label: "داشبورد",
|
||||
icon: "fi fi-rr-dashboard",
|
||||
to: "/admin/dashboard",
|
||||
key: 'panel',
|
||||
expanded: false,
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
label: "همه کیس ها",
|
||||
icon: "fi fi-rr-document",
|
||||
to: "",
|
||||
key: 'pets',
|
||||
permission: null,
|
||||
expanded: false,
|
||||
items: [
|
||||
{ label: "همه کیس ها", icon: "fi fi-rr-wifi-1", to: "/admin/pets" , permission: "pets.index" },
|
||||
{ label: "افزودن کیس جدید", icon: "fi fi-rr-wifi-1", to: "/admin/pets/create" , permission: "pets.create" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "همه پرونده ها",
|
||||
icon: "fi fi-rr-document",
|
||||
to: "",
|
||||
key: 'documents',
|
||||
permission: null,
|
||||
expanded: false,
|
||||
items: [
|
||||
{ label: "همه پرونده ها", icon: "fi fi-rr-wifi-1", to: "/admin/documents" , permission: "documents.index" },
|
||||
{ label: "افزودن پرونده جدید", icon: "fi fi-rr-wifi-1", to: "/admin/documents/create" , permission: "documents.create" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "همه گزارش ها",
|
||||
icon: "fi fi-rr-document",
|
||||
to: "/admin/reports",
|
||||
key: 'reports',
|
||||
permission: "reports.index",
|
||||
expanded: false,
|
||||
},
|
||||
{
|
||||
label: "مدیریت رسانه",
|
||||
icon: "fi fi-rr-gallery",
|
||||
to: "/admin/medium",
|
||||
key: 'medium',
|
||||
permission: "medium.index",
|
||||
},
|
||||
{
|
||||
label: "مدیریت دسترسی ها",
|
||||
icon: "fi fi-rr-user-permissions",
|
||||
key: 'roles',
|
||||
expanded: false,
|
||||
permission: null,
|
||||
items: [
|
||||
{ label: " مدیریت مقام ها", icon: "fi fi-rr-wifi-1", to: "/admin/roles", permission: "roles.index" },
|
||||
{ label: "مدیریت سطح ها", icon: "fi fi-rr-wifi-1", to: "/admin/permissions", permission: "permissions.index" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "کاربران",
|
||||
icon: "fi fi-rr-user",
|
||||
key: 'users',
|
||||
expanded: false,
|
||||
permission: null,
|
||||
items: [
|
||||
{ label: "همه کاربران", icon: "fi fi-rr-wifi-1", to: "/admin/users", permission: "users.index" },
|
||||
{ label: "افزودن کاربر جدید", icon: "fi fi-rr-wifi-1", to: "/admin/users/create", permission: "users.create" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "تنظیمات",
|
||||
icon: "fi fi-rr-settings",
|
||||
key: 'settings',
|
||||
expanded: false,
|
||||
permission: null,
|
||||
items: [
|
||||
{ label: "اطلاعات کاربری",icon: "fi fi-rr-wifi-1", to: "/admin/setting-profile", permission: null },
|
||||
{ label: "تنظیمات امنیتی", icon: "fi fi-rr-wifi-1", to: "/admin/setting-profile/security", permission: null },
|
||||
{ label: "دستگاه های فعال", icon: "fi fi-rr-wifi-1", to: "/admin/setting-profile/sessions", permission: null },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "خروج از حساب",
|
||||
icon: "fi fi-rr-sign-out-alt",
|
||||
to: "/admin/auth/logout",
|
||||
permission: null,
|
||||
},
|
||||
]);
|
||||
const toggleSubmenu = (item: SidebarItem) => {
|
||||
// expandedKeys.value = {};
|
||||
// item.expanded = !item.expanded;
|
||||
};
|
||||
|
||||
const closeSidebarOnClick = () => {
|
||||
globalStore.sidebarOpen = false;
|
||||
};
|
||||
|
||||
const expandedKeys = ref<Record<string, boolean>>({});
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
globalStore.current_user = currentUser;
|
||||
const saved = localStorage.getItem('expandedKeys')
|
||||
if (saved) expandedKeys.value = JSON.parse(saved)
|
||||
updateExpandedKeys()
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.p-panelmenu-content a')) {
|
||||
globalStore.sidebarOpen = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watch(expandedKeys, v => {
|
||||
localStorage.setItem('expandedKeys', JSON.stringify(v))
|
||||
})
|
||||
watch(() => url.value, () => {
|
||||
updateExpandedKeys()
|
||||
})
|
||||
|
||||
const isActive = (item: SidebarItem): boolean => {
|
||||
return expandedKeys.value[item.key] === true;
|
||||
};
|
||||
|
||||
const updateExpandedKeys = () => {
|
||||
const newKeys = {}
|
||||
const currentPath = url.value.split('?')[0];
|
||||
|
||||
items.value.forEach(item => {
|
||||
if (item.to === currentPath || item.items?.some(c => {
|
||||
const itemPath = c.to || '';
|
||||
return currentPath.startsWith(itemPath) && itemPath !== '';
|
||||
})) {
|
||||
newKeys[item.key] = true
|
||||
}
|
||||
})
|
||||
expandedKeys.value = newKeys
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="globalStore.sidebarOpen"
|
||||
class="fixed inset-0 bg-black/10 backdrop-blur-sm bg-opacity-50 z-10 sm:hidden"
|
||||
@click="globalStore.sidebarOpen = false">
|
||||
</div>
|
||||
<aside class="!bg-white shadow-all shadow-client-gray/5 w-[16rem]
|
||||
py-4 z-10 pb-8 fixed sm:relative inset-y-0 right-0 sm:inset-auto transition-transform duration-300 z-20"
|
||||
:class="[globalStore.sidebarOpen ? 'translate-x-0' : 'translate-x-full sm:translate-x-0']">
|
||||
<section class="sticky top-10 right-0 ">
|
||||
<div class="grid grid-cols-a border-b-[1px] border-client-gray/5 pb-6 justify-center gap-2 items-center">
|
||||
<figure class="grid grid-cols-a justify-center">
|
||||
<Avatar v-if="globalStore.current_user?.profile_url" :image="globalStore.current_user?.profile_url" size="xlarge" shape="circle" />
|
||||
<Avatar v-else image="/images/profile_default.png" size="xlarge" shape="circle" />
|
||||
</figure>
|
||||
<div class="text-xl grid grid-cols-a text-center text-client-Fontcolor font-semibold">
|
||||
<span class="text-lg font-extrabold mt-2">{{ (globalStore.current_user.first_name ?? '' ) + ' ' + (globalStore.current_user.last_name ?? '')}} </span>
|
||||
<span class="text-xs mt-1">{{ globalStore.current_user.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto h-full">
|
||||
<PanelMenu
|
||||
:pt="{
|
||||
header: 'custom-header',
|
||||
headerAction: 'custom-header-action',
|
||||
menuContent: 'custom-menu-content',
|
||||
menuitem: 'custom-menuitem',
|
||||
action: 'custom-action',
|
||||
icon: 'custom-icon',
|
||||
submenuIcon: 'custom-submenu-icon !text-xl',
|
||||
}"
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:model="filteredItems"
|
||||
class="custom-menu px-4">
|
||||
<template #item="{ item, root, hasSubmenu, toggle }">
|
||||
<div
|
||||
:class="(url.split('?')[0]) === item.to ? 'bg-client-primary !text-white font-bold' : 'text-client-Fontcolor'"
|
||||
class="flex items-center h-10 justify-between pl-2 text-client-Fontcolor rounded-lg cursor-pointer w-full"
|
||||
@click="hasSubmenu ? toggleSubmenu(item) : null">
|
||||
<div class="px-2 flex w-full items-center">
|
||||
<Link v-if="item.to" :href="item.to" class="flex items-center h-10 w-full" @click="globalStore.sidebarOpen = false">
|
||||
<i v-if="item.icon" :class="item.icon" class="flex !text-[1.2rem] -mt-0.5"></i>
|
||||
<div class="flex items-center font-normal text-sm p-2 gap-2 w-full">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</Link>
|
||||
<div v-else class="flex items-center h-10 w-full">
|
||||
<i v-if="item.icon" :class="item.icon" class="flex !text-[1.2rem] -mt-0.5"></i>
|
||||
<span class="w-full p-2 text-sm text-client-Fontcolor">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<i v-if="hasSubmenu"
|
||||
class="fi fi-rr-angle-small-left !text-sm flex transition-transform duration-300"
|
||||
:class="[{ 'rotate-90': item.expanded || isActive }]">
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
||||
</PanelMenu>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -0,0 +1,402 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import Pagination from './Pagination.vue';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
import Button from 'primevue/button';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Toolbar from 'primevue/toolbar';
|
||||
import Dropdown from 'primevue/dropdown';
|
||||
import MultiSelect from 'primevue/multiselect';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
searchValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
filters: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
filterLoader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'selection-change', 'page-change', 'search-change', 'filter-change', 'apply-filters', 'clear-filters']);
|
||||
|
||||
const localSelection = ref(props.modelValue);
|
||||
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
localSelection.value = newValue;
|
||||
}, { immediate: true });
|
||||
|
||||
const handleSelectionChange = (event) => {
|
||||
localSelection.value = event;
|
||||
emit('update:modelValue', event);
|
||||
emit('selection-change', event);
|
||||
};
|
||||
|
||||
const localSearchValue = ref(props.searchValue);
|
||||
|
||||
watch(() => props.searchValue, (newValue) => {
|
||||
localSearchValue.value = newValue;
|
||||
});
|
||||
|
||||
let searchDebounceTimer = null;
|
||||
|
||||
const handleSearchInput = () => {
|
||||
if (searchDebounceTimer) {
|
||||
clearTimeout(searchDebounceTimer);
|
||||
}
|
||||
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
emit('search-change', localSearchValue.value);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const localFilters = ref({ ...props.filters });
|
||||
const dynamicOptions = ref({});
|
||||
const loadingOptions = ref({});
|
||||
const searchDebounceTimers = ref({});
|
||||
const searchValues = ref({});
|
||||
|
||||
onMounted(() => {
|
||||
props.columns.forEach(column => {
|
||||
if (column.filter?.dynamic && props.filters[column.key]) {
|
||||
const filterValue = props.filters[column.key];
|
||||
searchValues.value[column.key] = filterValue.toString();
|
||||
loadDynamicOptions(column, true, filterValue.toString(), true);
|
||||
}
|
||||
else if (column.filter?.dynamic) {
|
||||
searchValues.value[column.key] = '';
|
||||
loadDynamicOptions(column, true, '', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watch(() => props.filters, (newValue) => {
|
||||
Object.keys(newValue).forEach(key => {
|
||||
if (newValue[key] !== undefined && newValue[key] !== null) {
|
||||
localFilters.value[key] = newValue[key];
|
||||
}
|
||||
});
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
const handleFilterChange = (columnKey, value) => {
|
||||
localFilters.value[columnKey] = value;
|
||||
emit('filter-change', { ...localFilters.value });
|
||||
};
|
||||
|
||||
const clearAllFilters = () => {
|
||||
localFilters.value = {};
|
||||
dynamicOptions.value = {};
|
||||
searchValues.value = {};
|
||||
emit('clear-filters');
|
||||
};
|
||||
|
||||
const loadDynamicOptions = async (column, forceReload = false, searchTerm = '', shouldSelectValue = false) => {
|
||||
if (!column.filter?.dynamic || !column.filter?.url) return;
|
||||
|
||||
const columnKey = column.key;
|
||||
loadingOptions.value[columnKey] = true;
|
||||
|
||||
try {
|
||||
let url = column.filter.url;
|
||||
if (searchTerm) {
|
||||
url += `?search=${encodeURIComponent(searchTerm)}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data) {
|
||||
dynamicOptions.value[columnKey] = data.data;
|
||||
|
||||
if (shouldSelectValue && props.filters[columnKey]) {
|
||||
const filterValue = props.filters[columnKey];
|
||||
const foundOption = data.data.find(option => option.value == filterValue);
|
||||
if (foundOption) {
|
||||
localFilters.value[columnKey] = foundOption.value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dynamicOptions.value[columnKey] = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
dynamicOptions.value[columnKey] = [];
|
||||
} finally {
|
||||
loadingOptions.value[columnKey] = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleFilterSearch = (column, searchTerm) => {
|
||||
if (!column.filter?.dynamic || !column.filter?.search) return;
|
||||
|
||||
const columnKey = column.key;
|
||||
searchValues.value[columnKey] = searchTerm;
|
||||
|
||||
if (searchDebounceTimers.value[columnKey]) {
|
||||
clearTimeout(searchDebounceTimers.value[columnKey]);
|
||||
}
|
||||
|
||||
searchDebounceTimers.value[columnKey] = setTimeout(() => {
|
||||
loadDynamicOptions(column, true, searchTerm, false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const renderFilter = (column) => {
|
||||
if (!column.filter) return null;
|
||||
|
||||
const filterConfig = column.filter;
|
||||
const currentValue = localFilters.value[column.key] || null;
|
||||
const columnKey = column.key;
|
||||
|
||||
let options = [];
|
||||
if (filterConfig.dynamic) {
|
||||
options = dynamicOptions.value[columnKey] || [];
|
||||
} else if (filterConfig.options) {
|
||||
options = filterConfig.options;
|
||||
}
|
||||
|
||||
if (filterConfig.type === 'dropdown') {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
options: loadingOptions.value[columnKey] ? [{ label: 'در حال پردازش...', value: null, disabled: true }] : options,
|
||||
value: currentValue,
|
||||
placeholder: filterConfig.placeholder || 'انتخاب کنید...',
|
||||
loading: loadingOptions.value[columnKey] || false,
|
||||
dynamic: filterConfig.dynamic || false,
|
||||
search: filterConfig.search || false,
|
||||
searchValue: searchValues.value[columnKey] || ''
|
||||
};
|
||||
} else if (filterConfig.type === 'multiselect') {
|
||||
return {
|
||||
type: 'multiselect',
|
||||
options: loadingOptions.value[columnKey] ? [{ label: 'در حال پردازش...', value: null, disabled: true }] : options,
|
||||
value: currentValue || [],
|
||||
placeholder: filterConfig.placeholder || 'انتخاب کنید...',
|
||||
loading: loadingOptions.value[columnKey] || false,
|
||||
dynamic: filterConfig.dynamic || false,
|
||||
search: filterConfig.search || false,
|
||||
searchValue: searchValues.value[columnKey] || ''
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderColumnContent = (column, data) => {
|
||||
let content = '';
|
||||
if (column.render && typeof column.render === 'function') {
|
||||
content = column.render(data);
|
||||
} else {
|
||||
content = data[column.key];
|
||||
}
|
||||
|
||||
const defaultStyle = '!font-medium !text-sm';
|
||||
const customStyle = column.style || defaultStyle;
|
||||
|
||||
return `<span class="${customStyle} text-right wrap-break-word text-sm">${content}</span>`;
|
||||
};
|
||||
|
||||
const getColumnStyle = (column) => {
|
||||
const baseStyle = { minWidth: '120px' };
|
||||
|
||||
if (column.width) {
|
||||
baseStyle.width = column.width + 'px';
|
||||
baseStyle.minWidth = column.width + 'px';
|
||||
baseStyle.maxWidth = column.width + 'px';
|
||||
}
|
||||
|
||||
return baseStyle;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table-generic">
|
||||
<div v-if="searchable" class="p-4 pb-2">
|
||||
<Toolbar class="">
|
||||
<template #start>
|
||||
<div class="grid grid-cols-2a max-sm:grid-cols-1 justify-start gap-2">
|
||||
<div class="grid grid-cols-1 items-center justify-start">
|
||||
<InputText
|
||||
size="small"
|
||||
class="placeholder:!font-light placeholder:!text-xs !h-10"
|
||||
placeholder="جستجو..."
|
||||
type="text"
|
||||
v-model="localSearchValue"
|
||||
@input="handleSearchInput"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="columns.some(col => col.filter)" class="">
|
||||
<div class="flex flex-wrap max-sm:!flex-col gap-2 py-2">
|
||||
<div
|
||||
v-for="column in columns.filter(col => col.filter)"
|
||||
:key="`filter-${column.key}`"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
|
||||
<Dropdown
|
||||
v-if="renderFilter(column)?.type === 'dropdown'"
|
||||
:modelValue="renderFilter(column).value"
|
||||
:options="renderFilter(column).options"
|
||||
:placeholder="renderFilter(column).loading ? 'در حال بارگذاری...' : renderFilter(column).placeholder"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
size="small"
|
||||
:pt="{label : '!text-[12px] !pt-2.5' , option : '!text-[12px] !font-normal'}"
|
||||
class="min-w-[130px] max-sm:!w-full max-w-[200px] !text-[12px] !h-10"
|
||||
:loading="renderFilter(column).loading"
|
||||
:disabled="renderFilter(column).loading"
|
||||
:filter="renderFilter(column).search"
|
||||
:filterValue="renderFilter(column).searchValue"
|
||||
:filterPlaceholder="renderFilter(column).search ? 'جستجو...' : ''"
|
||||
emptyMessage="داده ای یافت نشد"
|
||||
:filterMatchModeOptions="[]"
|
||||
:filterFunction="() => true"
|
||||
@update:modelValue="(value) => handleFilterChange(column.key, value)"
|
||||
@filter="(event) => handleFilterSearch(column, event.value)"
|
||||
showClear
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
v-else-if="renderFilter(column)?.type === 'multiselect'"
|
||||
:modelValue="renderFilter(column).value"
|
||||
:options="renderFilter(column).options"
|
||||
:placeholder="renderFilter(column).loading ? 'در حال بارگذاری...' : renderFilter(column).placeholder"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
size="small"
|
||||
:pt="{label : '!text-[12px] !pt-2.5' , option : '!text-[12px] !font-normal'}"
|
||||
class="min-w-[150px] max-sm:!w-full max-w-[200px] !text-[12px] !h-10"
|
||||
:loading="renderFilter(column).loading"
|
||||
:disabled="renderFilter(column).loading"
|
||||
:filter="renderFilter(column).search"
|
||||
:filterValue="renderFilter(column).searchValue"
|
||||
:filterPlaceholder="renderFilter(column).search ? 'جستجو...' : ''"
|
||||
emptyMessage="داده ای یافت نشد"
|
||||
:filterMatchModeOptions="[]"
|
||||
:filterFunction="() => true"
|
||||
@update:modelValue="(value) => handleFilterChange(column.key, value)"
|
||||
@filter="(event) => handleFilterSearch(column, event.value)"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
:label="filterLoader ? 'در حال پردازش...' : 'اعمال فیلتر'"
|
||||
size="small"
|
||||
severity="primary"
|
||||
class="!text-xs h-10"
|
||||
:loading="filterLoader"
|
||||
:disabled="filterLoader"
|
||||
@click="$emit('apply-filters')"
|
||||
/>
|
||||
<Button
|
||||
label="پاک کردن"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="!text-xs h-10"
|
||||
outlined
|
||||
@click="clearAllFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="localSelection && localSelection.length > 0" class="mr-2 !h-9 grid grid-cols-1">
|
||||
<slot name="bulk-action" :selection="localSelection"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #end>
|
||||
<slot name="toolbar-actions"></slot>
|
||||
</template>
|
||||
</Toolbar>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
:value="data"
|
||||
dataKey="id"
|
||||
v-model:selection="localSelection"
|
||||
:showGridlines="false"
|
||||
:stripedRows="false"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="flex justify-center py-4 items-center h-full">
|
||||
<span class="text-center text-sm">هیچ دادهای پیدا نشده!</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Column
|
||||
v-if="selectable"
|
||||
selectionMode="multiple"
|
||||
headerStyle="width: 4rem"
|
||||
/>
|
||||
|
||||
<Column
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
:field="column.key"
|
||||
:header="column.title"
|
||||
:sortable="column.hasSort === true"
|
||||
:style="getColumnStyle(column)"
|
||||
>
|
||||
<template #body="{ data }">
|
||||
<slot v-if="column.slot" :name="`column-${column.key}`" :data="data" :column="column">
|
||||
<div v-html="renderColumnContent(column, data)"></div>
|
||||
</slot>
|
||||
<div v-else v-html="renderColumnContent(column, data)"></div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<Pagination
|
||||
v-if="pagination && pagination.total"
|
||||
:total="pagination.total"
|
||||
:current-page="pagination.current_page"
|
||||
:perPage="pagination.per_page"
|
||||
:lastPage="pagination.last_page"
|
||||
:links="pagination.links"
|
||||
@page-change="(page) => emit('page-change', page)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.table-generic {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import vueDropzone from 'dropzone-vue3'
|
||||
import {useMediumStore} from "../../store/Medium.js";
|
||||
|
||||
const props = defineProps({
|
||||
uploadEndpoint: {
|
||||
type: String,
|
||||
default: '/admin/medium'
|
||||
}
|
||||
});
|
||||
|
||||
const dropzoneOptions = {
|
||||
url: props.uploadEndpoint,
|
||||
thumbnailWidth: 150,
|
||||
maxFilesize: 20,
|
||||
dictDefaultMessage: `
|
||||
<div class="!font-dana">
|
||||
<span class="font-black text-lg">آپلود رسانه</span>
|
||||
<p class="font-normal text-xs">حداکثر آپلود فایل 20 مگابایت میباشد</p>
|
||||
</div>
|
||||
`,
|
||||
headers : { "X-CSRF-TOKEN": document.head.querySelector("[name=csrf-token]").content}
|
||||
}
|
||||
|
||||
|
||||
const MediumStore = useMediumStore();
|
||||
const MediumSuccess = (response) => {
|
||||
if (response.xhr.response == 'noFormat'){
|
||||
alert('فرمت فایل نا معتبر است')
|
||||
}
|
||||
MediumStore.mediums.unshift(JSON.parse(response.xhr.response));
|
||||
console.log(JSON.parse(response.xhr.response))
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vue-dropzone
|
||||
ref="myVueDropzone"
|
||||
id="dropzone"
|
||||
class="!border-dashed !bg-transparent overflow-y-auto max-h-[12rem]"
|
||||
:options="dropzoneOptions"
|
||||
v-on:vdropzone-success="MediumSuccess"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,174 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref, watch} from "vue";
|
||||
import {useMediumStore} from "../../store/Medium.js";
|
||||
|
||||
import Media from "./Media.vue";
|
||||
import PaginationAjax from "../PaginationAjax.vue";
|
||||
import AddMedium from "./AddMedia.vue";
|
||||
const MediumStore = useMediumStore();
|
||||
const emit = defineEmits(['update:modelValue', 'remove-item']);
|
||||
const props = defineProps({
|
||||
modelValue: { type: [Boolean, Array], required: true },
|
||||
multiple: { type: Boolean, default: false },
|
||||
uploadEndpoint: { type: String, default: '/admin/upload-medium' }
|
||||
});
|
||||
|
||||
const selectImage = ref({});
|
||||
const selectedImages = ref([]);
|
||||
const isChooseMedium = ref(false);
|
||||
|
||||
const visible_modal_mediums = ref(false);
|
||||
const hasLoaded = ref(false);
|
||||
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (props.multiple) {
|
||||
selectedImages.value = Array.isArray(newVal) ? newVal : [];
|
||||
isChooseMedium.value = selectedImages.value.length > 0;
|
||||
} else {
|
||||
selectImage.value = newVal;
|
||||
isChooseMedium.value = newVal && newVal.url ? true : false;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const diselect_medium = () => {
|
||||
if (props.multiple) {
|
||||
selectedImages.value = [];
|
||||
isChooseMedium.value = false;
|
||||
emit('update:modelValue', []);
|
||||
} else {
|
||||
selectImage.value = {};
|
||||
isChooseMedium.value = false;
|
||||
emit('update:modelValue', selectImage.value);
|
||||
}
|
||||
}
|
||||
const getMediums = (page = 1) => {
|
||||
axios.get(`/admin/get-medium?page=${page}`).then(res => {
|
||||
MediumStore.mediums = res.data.data ?? [];
|
||||
MediumStore.paginate.total = res.data.total ?? null;
|
||||
MediumStore.paginate.last_page = res.data.last_page ?? null;
|
||||
MediumStore.paginate.current_page = res.data.current_page ?? null;
|
||||
MediumStore.paginate.links = res.data.links ?? null;
|
||||
MediumStore.paginate.per_page = res.data.per_page ?? null;
|
||||
hasLoaded.value = true;
|
||||
})
|
||||
}
|
||||
|
||||
const toggle_select_image = (medium, event) => {
|
||||
if (props.multiple) {
|
||||
const isCtrlPressed = event.ctrlKey || event.metaKey;
|
||||
const existingIndex = selectedImages.value.findIndex(c => c.id === medium.id);
|
||||
|
||||
if (existingIndex > -1) {
|
||||
selectedImages.value.splice(existingIndex, 1);
|
||||
} else {
|
||||
if (isCtrlPressed) {
|
||||
selectedImages.value.push(medium);
|
||||
} else {
|
||||
selectedImages.value = [medium];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectImage.value != medium) {
|
||||
selectImage.value = medium;
|
||||
} else {
|
||||
selectImage.value = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chooseMedium = () => {
|
||||
isChooseMedium.value = true;
|
||||
if (props.multiple) {
|
||||
emit('update:modelValue', selectedImages.value);
|
||||
} else {
|
||||
emit('update:modelValue', selectImage.value);
|
||||
}
|
||||
toggle_show_modal();
|
||||
}
|
||||
const toggle_show_modal = () => {
|
||||
visible_modal_mediums.value = !visible_modal_mediums.value;
|
||||
|
||||
if (!hasLoaded.value){
|
||||
getMediums();
|
||||
}
|
||||
}
|
||||
|
||||
const removeSelectedImage = (index) => {
|
||||
emit('remove-item', index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card flex justify-center">
|
||||
<div @click="toggle_show_modal" class="m-auto mt-6 grid grid-cols-a shadow-lg items-center border-3 border-client-gray/5 relative w-[14rem] content-center justify-center
|
||||
rounded-2xl h-[14rem] cursor-pointer text-center text-client-Fontcolor/90 shadow-client-gray/10">
|
||||
<div v-if="!isChooseMedium && (!selectImage?.url && selectedImages.length === 0)" class="grid grid-cols-a">
|
||||
<div class="flex justify-center m-auto bg-client-gray/10 p-2 rounded-2xl">
|
||||
<i class="fi fi-rr-cloud-upload-alt flex text-client-Fontcolor/80 m-auto text-4xl"></i>
|
||||
</div>
|
||||
<div class="absolute -top-3 -right-3 shadow-all p-3 rounded-3xl">
|
||||
<i class="fi fi-rr-add-image flex text-xl"></i>
|
||||
</div>
|
||||
<span class="block text-sm text-client-Fontcolor/80 font-black mt-3">
|
||||
{{ multiple ? 'انتخاب یا بارگذاری فایلها' : 'انتخاب یا بارگذاری تصویر کاربر' }}
|
||||
</span>
|
||||
<span class="block text-xs mt-1">حداکثر آپلود ۲ مگابایت میباشد</span>
|
||||
</div>
|
||||
<div v-else-if="multiple && selectedImages.length > 0" class="p-2">
|
||||
<h1 class="font-bold text-sm">
|
||||
انتخاب فایل های بیشتر
|
||||
</h1>
|
||||
<div class="text-center mt-2">
|
||||
<span class="text-xs text-gray-500">{{ selectedImages.length }} فایل انتخاب شده</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!multiple && selectImage?.url">
|
||||
<figure class="h-[13rem] px-4 py-0">
|
||||
<img class="w-full h-full object-contain" :src="selectImage.url" :alt="selectImage.filename">
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog v-model:visible="visible_modal_mediums" maximizable modal header="انتخاب رسانه"
|
||||
:style="{ width: '75vw' , minHeight: '45rem' }"
|
||||
:pt="{header : '!bg-client-primaryBlue'}"
|
||||
:breakpoints="{ '1199px': '75vw', '575px': '90vw' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h2 class="font-black text-lg">نمایش همه رسانه</h2>
|
||||
<div class="flex gap-2 px-6" v-if="(selectImage && selectImage?.id) || (multiple && selectedImages.length > 0)">
|
||||
<Button @click="chooseMedium" label="انتخاب میکنم" variant="outlined" size="small" severity="" class="" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid grid-cols-1 overflow-y-auto max-h-[12rem]">
|
||||
<add-medium class="h-full w-full" :upload-endpoint="uploadEndpoint"></add-medium>
|
||||
</div>
|
||||
<div id="startModal" class="grid grid-cols-[repeat(auto-fill,_minmax(8rem,11rem))] mt-3 gap-4 justify-center">
|
||||
<Media :action="false" @click="toggle_select_image(medium, $event)" v-for="(medium , index) in MediumStore.mediums"
|
||||
:medium="medium" :class="['border-[2px] border-transparent transition-all',
|
||||
{'!border-[2px] !border-client-primary' : multiple ? selectedImages.some(c => c.id === medium.id) : medium.id == selectImage.id }]" :key="index"></Media>
|
||||
</div>
|
||||
<PaginationAjax
|
||||
:onPaginate="(page) => getMediums(page)"
|
||||
top-scroll="startModal"
|
||||
:total="MediumStore.paginate.total"
|
||||
:current-page="MediumStore.paginate.current_page"
|
||||
:perPage="MediumStore.paginate.per_page"
|
||||
:lastPage="MediumStore.paginate.last_page"
|
||||
:links="MediumStore.paginate.links">
|
||||
</PaginationAjax>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<div v-if="((selectImage && selectImage?.id) || (multiple && selectedImages.length > 0)) && isChooseMedium" class="grid mt-4 grid-cols-a justify-center gap-2">
|
||||
<Button @click="diselect_medium" variant="outlined" size="small" severity="secondary" class="flex m-auto" label="لغو انتخاب"/>
|
||||
<div v-if="multiple && selectedImages.length > 0" class="space-y-1">
|
||||
<div v-for="(image, index) in selectedImages" :key="index" class="flex items-center justify-between bg-gray-100 p-2 rounded text-sm">
|
||||
<span class="truncate text-xs px-4">{{ image.filename }}</span>
|
||||
<Button icon="fi fi-rr-trash" size="small" severity="danger" text @click="removeSelectedImage(index)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import {useMediumStore} from "../../store/Medium.js";
|
||||
import Pagination from "../Pagination.vue";
|
||||
import Media from "./Media.vue";
|
||||
|
||||
const MediumStore = useMediumStore();
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="grid grid-cols-1 mt-4">
|
||||
<div class="grid grid-cols-5 max-xl:grid-cols-4 max-lg:grid-cols-3 max-md:grid-cols-2 gap-4 justify-center max-sm:grid-cols-1">
|
||||
<Media :action="true" v-for="medium in MediumStore.mediums" :medium="medium" :key="medium.id"
|
||||
:class="{'active' : medium.id === MediumStore.selected }"></Media>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<Pagination
|
||||
:total="MediumStore.paginate.total"
|
||||
:current-page="MediumStore.paginate.current_page"
|
||||
:perPage="MediumStore.paginate.per_page"
|
||||
:lastPage="MediumStore.paginate.last_page"
|
||||
:links="MediumStore.paginate.links"></Pagination>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="css">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {useMediumStore} from "../../store/Medium.js";
|
||||
import {Inertia} from "@inertiajs/inertia";
|
||||
const MediumStore = useMediumStore();
|
||||
const hasCopyUrl = ref(false);
|
||||
const props = defineProps({
|
||||
medium : {
|
||||
type:Object
|
||||
},
|
||||
action : {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const copyImageUrl = (id) => {
|
||||
let testingCodeToCopy = document.getElementById(`mediumUrlCopy_${id}`);
|
||||
testingCodeToCopy.setAttribute('type', 'value');
|
||||
testingCodeToCopy.select();
|
||||
document.execCommand('copy')
|
||||
testingCodeToCopy.setAttribute('type', 'hidden');
|
||||
hasCopyUrl.value = id;
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
|
||||
const singleDelete = (id) => {
|
||||
Inertia.delete(`/admin/medium/${id}`, {
|
||||
onSuccess: () => {
|
||||
MediumStore.mediums = MediumStore.mediums.filter(medium => medium.id !== id);
|
||||
}
|
||||
}, { replace: false });
|
||||
}
|
||||
|
||||
const getImageUrl = (medium) => {
|
||||
if (medium.disk === 'public') {
|
||||
return medium.url;
|
||||
}
|
||||
|
||||
if (medium.disk === 'private') {
|
||||
return `/admin/medium/${medium.id}/serve`;
|
||||
}
|
||||
|
||||
return medium.url;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-1 bg-white border-[1px] border-gray-100 shadow-all cursor-pointer rounded-2xl">
|
||||
<input :id="`mediumUrlCopy_${medium.id}`" type="hidden" :value="getImageUrl(medium)"/>
|
||||
<figure class="p-3 h-[10rem] content-center">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-if="medium.mime_type == 'mp4' || medium.mime_type == 'mkv'" src="@/images/musicFolder.png">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'mp3'" src="@/images/musicFolder.png">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/zip' || medium.mime_type == 'application/rar'" src="@/images/zip.ico">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/pdf'" src="@/images/pdf.webp">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'application/txt'" src="@/images/txtFormat.png">
|
||||
<img class="w-full h-full rounded-2xl object-contain" v-else-if="medium.mime_type == 'sql'" src="@/images/1769_-_SQL-512.png">
|
||||
<img class="w-full h-full object-contain rounded-2xl" v-else :src="getImageUrl(medium)">
|
||||
</figure>
|
||||
<div class="px-4 text-xs grid grid-cols-1 gap-2 mb-4">
|
||||
<div class="grid grid-cols-2a justify-between gap-2 items-center">
|
||||
<span class="text-[.67rem]">نام فایل:</span>
|
||||
<span class="text-xs font-light w-full truncate">{{medium.filename}}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-between gap-2 items-center">
|
||||
<span class="text-[.67rem]">سایز فایل:</span>
|
||||
<span class="text-xs font-light w-full truncate"> {{medium.size}}kb </span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-between gap-2 items-center">
|
||||
<span class="text-[.67rem]">فرمت فایل:</span>
|
||||
<span class="text-xs font-light w-full truncate">{{medium.mime_type}}</span>
|
||||
</div>
|
||||
<div v-if="action" class="grid grid-cols-1 justify-between gap-2 items-center" :class="{ '!grid-cols-2' : action}">
|
||||
<Button type="button"
|
||||
@click="singleDelete(medium.id)" severity="danger"
|
||||
label="حذف"
|
||||
outlined
|
||||
class="!text-xs"
|
||||
size="small"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"/>
|
||||
<Button @click="copyImageUrl(medium.id)" type="button" class="!text-xs"
|
||||
severity="secondary"
|
||||
size="small"
|
||||
:label="hasCopyUrl == medium.id ? '✓ کپی شد ' : 'کپی آدرس'"
|
||||
:pt="{icon : '!mt-2 !text-xs'}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="css">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,248 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import UISelect from '../ui/UISelect.vue'
|
||||
import { useReportStore } from '../../store/Report.js'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const props = defineProps({
|
||||
reportData: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel'])
|
||||
const toast = useToast()
|
||||
|
||||
const form = ref({
|
||||
document_id: null,
|
||||
doctor_id: null,
|
||||
medium_id: null,
|
||||
status: 'Draft',
|
||||
body: '',
|
||||
submitted_at: null
|
||||
})
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
||||
const quillToolbar = computed(() => {
|
||||
return props.isFullscreen ? 'full' : 'essential'
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ label: 'پیشنویس', value: 'Draft' },
|
||||
{ label: 'در حال انجام', value: 'InProgress' },
|
||||
{ label: 'تکمیل شده', value: 'Completed' },
|
||||
{ label: 'بررسی شده', value: 'Reviewed' },
|
||||
{ label: 'تایید شده', value: 'Approved' },
|
||||
{ label: 'رد شده', value: 'Rejected' }
|
||||
]
|
||||
|
||||
const documentOptions = ref([])
|
||||
const doctorOptions = ref([])
|
||||
const mediaOptions = ref([])
|
||||
const isLoadingOptions = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
loadInitialOptions()
|
||||
|
||||
if (props.isEdit && props.reportData) {
|
||||
populateFormWithData()
|
||||
addCurrentToOptions()
|
||||
}
|
||||
})
|
||||
|
||||
const loadInitialOptions = async () => {
|
||||
try {
|
||||
isLoadingOptions.value = true
|
||||
|
||||
const documentsResponse = await axios.get('/admin/documents-options')
|
||||
if (documentsResponse.data.success) {
|
||||
documentOptions.value = documentsResponse.data.data
|
||||
}
|
||||
|
||||
const doctorsResponse = await axios.get('/admin/doctors-options')
|
||||
if (doctorsResponse.data.success) {
|
||||
doctorOptions.value = doctorsResponse.data.data
|
||||
}
|
||||
|
||||
mediaOptions.value = []
|
||||
} catch (error) {
|
||||
console.error('Error loading initial options:', error)
|
||||
} finally {
|
||||
isLoadingOptions.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const populateFormWithData = () => {
|
||||
if (props.reportData) {
|
||||
const statusOption = statusOptions.find(option => option.label === props.reportData.status)
|
||||
const statusValue = statusOption ? statusOption.value : 'Draft'
|
||||
|
||||
form.value = {
|
||||
document_id: props.reportData.document?.id || null,
|
||||
doctor_id: props.reportData.doctor?.id || null,
|
||||
medium_id: props.reportData.medium_id || null,
|
||||
status: statusValue,
|
||||
body: props.reportData.body || '',
|
||||
submitted_at: props.reportData.submitted_at_form
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addCurrentToOptions = () => {
|
||||
if (props.reportData) {
|
||||
if (props.reportData.document && !documentOptions.value.find(opt => opt.value === props.reportData.document.id)) {
|
||||
documentOptions.value.unshift({
|
||||
label: `پرونده ${props.reportData.document.code}`,
|
||||
value: props.reportData.document.id
|
||||
})
|
||||
}
|
||||
|
||||
if (props.reportData.doctor && !doctorOptions.value.find(opt => opt.value === props.reportData.doctor.id)) {
|
||||
doctorOptions.value.unshift({
|
||||
label: props.reportData.doctor.fullname,
|
||||
value: props.reportData.doctor.id
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
reportStore.createForm = form.value
|
||||
await emit('submit', props.isEdit ? props.reportData.id : null)
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
resetForm()
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.value = {
|
||||
document_id: null,
|
||||
doctor_id: null,
|
||||
medium_id: null,
|
||||
status: 'Draft',
|
||||
body: '',
|
||||
submitted_at: null
|
||||
}
|
||||
loadInitialOptions()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6 overflow-hidden">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
انتخاب پرونده
|
||||
</label>
|
||||
<UISelect
|
||||
v-model="form.document_id"
|
||||
:options="documentOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
searchUrl="/admin/documents-options"
|
||||
searchParam="search"
|
||||
:placeholder="isLoadingOptions ? 'در حال بارگذاری...' : 'انتخاب پرونده'"
|
||||
:filter="true"
|
||||
@update:options="documentOptions = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
انتخاب پزشک
|
||||
</label>
|
||||
<UISelect
|
||||
v-model="form.doctor_id"
|
||||
:options="doctorOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
searchUrl="/admin/doctors-options"
|
||||
searchParam="search"
|
||||
:placeholder="isLoadingOptions ? 'در حال بارگذاری...' : ' انتخاب پزشک'"
|
||||
:filter="true"
|
||||
@update:options="doctorOptions = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
وضعیت
|
||||
</label>
|
||||
<UISelect
|
||||
v-model="form.status"
|
||||
:options="statusOptions"
|
||||
optionValue="value"
|
||||
optionLabel="label"
|
||||
placeholder="انتخاب وضعیت"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
تاریخ ارسال
|
||||
</label>
|
||||
<CustomDatePicker
|
||||
v-model="form.submitted_at"
|
||||
type="datetime"
|
||||
compact-time
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
محتوای گزارش
|
||||
</label>
|
||||
<div class="text-right w-full max-w-full">
|
||||
<quill-editor
|
||||
:key="`quill-${props.isFullscreen ? 'full' : 'essential'}`"
|
||||
theme="snow"
|
||||
:toolbar="quillToolbar"
|
||||
:content="form.body"
|
||||
@update:content="form.body = $event"
|
||||
content-type="html"
|
||||
placeholder="محتوای گزارش را اینجا بنویسید..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 border-t border-gray-200">
|
||||
<Button
|
||||
class="!text-sm"
|
||||
label="انصراف"
|
||||
severity="secondary"
|
||||
@click="handleCancel"
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
<Button
|
||||
class="!text-sm"
|
||||
:label="isEdit ? 'ویرایش گزارش' : 'ایجاد گزارش'"
|
||||
@click="handleSubmit"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white border border-gray-200 rounded-2xl p-4" :class="class">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,154 @@
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maximizable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
validator: (value) => ['small', 'medium', 'large', 'xlarge'].includes(value)
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
dismissableMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
modal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'hide', 'show', 'fullscreen-change'])
|
||||
|
||||
const isVisible = ref(props.visible)
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
watch(() => props.visible, (newValue) => {
|
||||
isVisible.value = newValue
|
||||
})
|
||||
|
||||
watch(isVisible, (newValue) => {
|
||||
emit('update:visible', newValue)
|
||||
if (newValue) {
|
||||
emit('show')
|
||||
nextTick(() => {
|
||||
checkFullscreenState()
|
||||
})
|
||||
} else {
|
||||
emit('hide')
|
||||
}
|
||||
})
|
||||
|
||||
const checkFullscreenState = () => {
|
||||
const dialog = document.querySelector('.p-dialog:not(.p-dialog-hidden)')
|
||||
if (dialog) {
|
||||
const rect = dialog.getBoundingClientRect()
|
||||
const isMaximized = rect.width >= window.innerWidth * 0.95 && rect.height >= window.innerHeight * 0.95
|
||||
|
||||
if (isMaximized !== isFullscreen.value) {
|
||||
isFullscreen.value = isMaximized
|
||||
emit('fullscreen-change', isMaximized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
if (isVisible.value) {
|
||||
checkFullscreenState()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
const hide = () => {
|
||||
isVisible.value = false
|
||||
}
|
||||
|
||||
const show = () => {
|
||||
isVisible.value = true
|
||||
}
|
||||
|
||||
const handleMaximize = () => {
|
||||
isFullscreen.value = true
|
||||
emit('fullscreen-change', true)
|
||||
}
|
||||
|
||||
const handleUnmaximize = () => {
|
||||
isFullscreen.value = false
|
||||
emit('fullscreen-change', false)
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
small: 'max-w-md',
|
||||
medium: 'max-w-lg',
|
||||
large: 'max-w-2xl',
|
||||
xlarge: 'max-w-3xl'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
hide
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
v-model:visible="isVisible"
|
||||
:modal="modal"
|
||||
:closable="closable"
|
||||
:dismissableMask="dismissableMask"
|
||||
:maximizable="maximizable"
|
||||
@hide="hide"
|
||||
@show="show"
|
||||
@maximize="handleMaximize"
|
||||
@unmaximize="handleUnmaximize"
|
||||
:class="[!maximizable ? sizeClasses[size] : '', props.class]"
|
||||
:pt="{
|
||||
root: 'backdrop-blur-sm',
|
||||
header: 'border-b border-gray-200',
|
||||
content: 'p-0',
|
||||
footer: 'border-t border-gray-200',
|
||||
}"
|
||||
>
|
||||
<template #header v-if="title">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ title }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="p-6">
|
||||
<slot :isFullscreen="isFullscreen" />
|
||||
</div>
|
||||
|
||||
<template #footer v-if="$slots.footer">
|
||||
<div class="flex justify-end gap-3">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,165 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Array, Object],
|
||||
default: null
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
optionValue: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
optionLabel: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'انتخاب کنید'
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
searchParam: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: '!text-xs !h-full'
|
||||
},
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
option: '!text-sm',
|
||||
placeholder: '!text-sm',
|
||||
label: '!pt-3'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'filter', 'change', 'update:options'])
|
||||
|
||||
const isLoading = ref(false)
|
||||
const searchTimeout = ref(null)
|
||||
|
||||
const handleFilter = (event) => {
|
||||
if (searchTimeout.value) {
|
||||
clearTimeout(searchTimeout.value)
|
||||
}
|
||||
|
||||
const searchValue = event.value ? event.value.trim() : ''
|
||||
|
||||
// If search is empty, make request immediately (no debounce)
|
||||
if (!searchValue) {
|
||||
isLoading.value = true
|
||||
|
||||
if (props.searchUrl) {
|
||||
axios.get(props.searchUrl, {
|
||||
params: {}
|
||||
}).then(response => {
|
||||
if (response.data.success) {
|
||||
emit('update:options', response.data.data)
|
||||
}
|
||||
isLoading.value = false
|
||||
}).catch(error => {
|
||||
console.error('Search error:', error)
|
||||
isLoading.value = false
|
||||
})
|
||||
} else {
|
||||
emit('filter', event)
|
||||
isLoading.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If search has value, use debounce
|
||||
isLoading.value = true
|
||||
|
||||
searchTimeout.value = setTimeout(() => {
|
||||
if (props.searchUrl) {
|
||||
const params = {}
|
||||
params[props.searchParam] = searchValue
|
||||
|
||||
axios.get(props.searchUrl, {
|
||||
params: params
|
||||
}).then(response => {
|
||||
if (response.data.success) {
|
||||
const newOptions = response.data.data
|
||||
const currentValue = props.modelValue
|
||||
const currentOption = props.options.find(opt => {
|
||||
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
|
||||
return value == currentValue
|
||||
})
|
||||
|
||||
let finalOptions = newOptions
|
||||
if (currentOption && !newOptions.find(opt => {
|
||||
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
|
||||
return value == currentValue
|
||||
})) {
|
||||
finalOptions = [currentOption, ...newOptions]
|
||||
}
|
||||
|
||||
emit('update:options', finalOptions)
|
||||
}
|
||||
isLoading.value = false
|
||||
}).catch(error => {
|
||||
console.error('Search error:', error)
|
||||
isLoading.value = false
|
||||
})
|
||||
} else {
|
||||
emit('filter', event)
|
||||
isLoading.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleChange = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
return () => {
|
||||
if (searchTimeout.value) {
|
||||
clearTimeout(searchTimeout.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select
|
||||
:class="class"
|
||||
:placeholder="placeholder"
|
||||
:modelValue="modelValue"
|
||||
:options="options"
|
||||
:optionValue="optionValue"
|
||||
:optionLabel="optionLabel"
|
||||
:multiple="multiple"
|
||||
:filter="filter"
|
||||
:loading="loading || isLoading"
|
||||
:pt="pt"
|
||||
@update:modelValue="handleChange"
|
||||
@filter="handleFilter"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmDialog group="deleteTemplate">
|
||||
<template #message="slotProps">
|
||||
<div class="flex flex-col items-center w-full gap-4">
|
||||
<i :class="slotProps.message.icon" class="!text-6xl text-primary-500"></i>
|
||||
<p class="leading-7 text-sm font-normal">{{ slotProps.message.message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
import HeaderComponent from "../components/HeaderComponent.vue";
|
||||
import SidebarComponent from "../components/SidebarComponent.vue";
|
||||
import DialogsProvider from "./DialogsProvider.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="">
|
||||
<Toast />
|
||||
<header-component></header-component>
|
||||
<section class="grid grid-cols-1 sm:grid-cols-[16rem_1fr]">
|
||||
<sidebar-component></sidebar-component>
|
||||
<main class="overflow-hidden min-h-[calc(100svh-4rem)] p-4 pt-0 grid grid-cols-1">
|
||||
<section class="rounded-2xl content-start pt-4 grid grid-cols-1">
|
||||
<div v-if="$page.props.flash.success" class="w-full mb-4">
|
||||
<Message size="small" closable class="!text-xs py-1.5" severity="success">{{$page.props.flash.success}}</Message>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<dialogs-provider></dialogs-provider>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
export const useAuthStore = defineStore('AuthStore', {
|
||||
state: () => ({
|
||||
loginForm:{
|
||||
email : '',
|
||||
password : '',
|
||||
remember : false,
|
||||
},
|
||||
registerForm:{
|
||||
name : '',
|
||||
email : '',
|
||||
password : '',
|
||||
password_confirmation : '',
|
||||
},
|
||||
loaderLoginForm : false,
|
||||
loaderRegisterForm : false,
|
||||
errorsLoginForm : [],
|
||||
errorsRegisterForm : [],
|
||||
}),
|
||||
actions: {
|
||||
login() {
|
||||
this.loaderLoginForm = true;
|
||||
axios.post('/admin/auth/login' , this.loginForm).then(res => {
|
||||
this.resetLoginForm();
|
||||
this.loaderLoginForm = false;
|
||||
this.errorsLoginForm = [];
|
||||
Inertia.get('/');
|
||||
}).catch(error => {
|
||||
this.loaderLoginForm = false;
|
||||
this.errorsLoginForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
register(){
|
||||
this.loaderRegisterForm = true;
|
||||
axios.post('/admin/auth/register' , this.registerForm).then(res => {
|
||||
this.resetRegisterForm();
|
||||
this.loaderRegisterForm = false;
|
||||
this.errorsRegisterForm = [];
|
||||
Inertia.get('/');
|
||||
}).catch(error => {
|
||||
this.loaderRegisterForm = false;
|
||||
this.errorsRegisterForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
resetLoginForm() {
|
||||
this.loginForm = {
|
||||
email : '',
|
||||
password : '',
|
||||
remember : false,
|
||||
}
|
||||
},
|
||||
resetRegisterForm() {
|
||||
this.registerForm = {
|
||||
name : '',
|
||||
email : '',
|
||||
password : '',
|
||||
password_confirmation : '',
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,303 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export const useDocumentStore = defineStore('DocumentStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
modality: '',
|
||||
status: '',
|
||||
priority: '',
|
||||
note: '',
|
||||
pet_id: '',
|
||||
user_id: '',
|
||||
submitted_at: '',
|
||||
scan_date: '',
|
||||
processing_deadline: '',
|
||||
primary_files: [],
|
||||
history_files: [],
|
||||
},
|
||||
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'code',
|
||||
title: 'شناسه پرونده',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
key: 'modality',
|
||||
title: 'نوع درخواست',
|
||||
width: 130,
|
||||
render: (data) => `<pre dir="ltr" class="text-right">${data.modality}</pre>`,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'وضعیت',
|
||||
slot: 'status',
|
||||
filter: {
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'ثبت شده', value: 'Registered' },
|
||||
{ label: 'در حال انجام', value: 'InProgress' },
|
||||
{ label: 'تکمیل شده', value: 'Completed' },
|
||||
{ label: 'رد شده', value: 'Rejected' },
|
||||
{ label: 'لغو شده', value: 'Cancelled' }
|
||||
],
|
||||
placeholder: 'انتخاب وضعیت...'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'pet',
|
||||
title: 'کیس',
|
||||
slot: 'pet',
|
||||
filter: {
|
||||
type: 'dropdown',
|
||||
dynamic: true,
|
||||
search: true,
|
||||
url: '/admin/pets-options',
|
||||
placeholder: 'انتخاب کیس...'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'requester',
|
||||
title: 'درخواست دهنده',
|
||||
width: 200,
|
||||
slot: 'requester',
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
title: 'اولویت',
|
||||
slot: 'priority',
|
||||
filter: {
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'عادی', value: 'Medium' },
|
||||
{ label: 'اورژانسی', value: 'High' },
|
||||
],
|
||||
placeholder: 'انتخاب اولویت...'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'submitted_at',
|
||||
title: 'تاریخ ثبت',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
render:(data) => data.submitted_at ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'processing_deadline',
|
||||
title: 'تاریخ مهلت گزارش',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
render:(data) => data.processing_deadline ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
},
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/documents',
|
||||
create: '/admin/documents/create',
|
||||
store: '/admin/documents',
|
||||
edit: (id) => `/admin/documents/${id}/edit`,
|
||||
update: (id) => `/admin/documents/${id}`,
|
||||
delete: (id) => `/admin/documents/${id}`,
|
||||
bulkDelete: '/admin/documents-bulk-delete',
|
||||
export: '/admin/documents-export-data'
|
||||
},
|
||||
statusOptions: [
|
||||
{ label: 'ثبت شده', value: 'Registered' },
|
||||
{ label: 'در حال انجام', value: 'InProgress' },
|
||||
{ label: 'تکمیل شده', value: 'Completed' },
|
||||
{ label: 'رد شده', value: 'Rejected' },
|
||||
{ label: 'لغو شده', value: 'Cancelled' },
|
||||
],
|
||||
priorityOptions: [
|
||||
{ label: 'عادی', value: 'Medium' },
|
||||
{ label: 'اورژانسی', value: 'High' },
|
||||
],
|
||||
modalityOptions: [
|
||||
{ label: 'CT', value: 'CT' },
|
||||
{ label: 'MRI', value: 'MRI' },
|
||||
{ label: 'X-Ray', value: 'X-Ray' },
|
||||
{ label: 'Ultrasound', value: 'Ultrasound' },
|
||||
{ label: 'PET', value: 'PET' },
|
||||
{ label: 'other', value: 'Other' },
|
||||
],
|
||||
petOptions: [],
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش پرونده' : 'ثبت و ایجاد پرونده جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش پرونده' : 'افزودن پرونده جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه پرونده ها';
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
update_and_store() {
|
||||
const url = this.is_edit_id ?
|
||||
this.endpoints.update(this.is_edit_id) :
|
||||
this.endpoints.store;
|
||||
const method = this.is_edit_id ? 'patch' : 'post';
|
||||
|
||||
this.loaderCreateForm = true;
|
||||
this.createForm.primary_files = this.createForm.primary_files.map(file => file.id);
|
||||
this.createForm.history_files = this.createForm.history_files.map(file => file.id);
|
||||
axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: this.createForm
|
||||
}).then(res => {
|
||||
this.errorsCreateForm = []
|
||||
this.loaderCreateForm = false;
|
||||
if (this.is_edit_id) {
|
||||
Inertia.get(this.endpoints.edit(this.is_edit_id));
|
||||
} else {
|
||||
this.resetCreateForm();
|
||||
Inertia.get(this.endpoints.create);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
modality: '',
|
||||
status: '',
|
||||
priority: '',
|
||||
note: '',
|
||||
pet_id: '',
|
||||
user_id: '',
|
||||
submitted_at: '',
|
||||
scan_date: '',
|
||||
processing_deadline: '',
|
||||
primary_files: [],
|
||||
history_files: [],
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'submitted_at' && data.submitted_at) {
|
||||
this.createForm[field] = data.submitted_at;
|
||||
} else if (field === 'scan_date' && data.scan_date) {
|
||||
this.createForm[field] = data.scan_date;
|
||||
} else if (field === 'processing_deadline' && data.processing_deadline) {
|
||||
this.createForm[field] = data.processing_deadline;
|
||||
} else if (field === 'created_at' && data.created_at) {
|
||||
this.createForm[field] = data.created_at;
|
||||
} else if (field === 'primary_files' && data.primary_files) {
|
||||
this.createForm[field] = data.primary_files;
|
||||
} else if (field === 'history_files' && data.history_files) {
|
||||
this.createForm[field] = data.history_files;
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
},
|
||||
|
||||
getStatusSeverity(status) {
|
||||
const statusSeverities = {
|
||||
'ثبت شده': 'info',
|
||||
'در حال انجام': 'warning',
|
||||
'تکمیل شده': 'success',
|
||||
'رد شده': 'danger',
|
||||
'لغو شده': 'secondary'
|
||||
};
|
||||
return statusSeverities[status] || 'secondary';
|
||||
},
|
||||
|
||||
getPrioritySeverity(priority) {
|
||||
const prioritySeverities = {
|
||||
'عادی': 'info',
|
||||
'اورژانسی': 'danger'
|
||||
};
|
||||
return prioritySeverities[priority] || 'secondary';
|
||||
},
|
||||
|
||||
convertFiltersToArrayParams(filters) {
|
||||
const params = {};
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (Array.isArray(filters[key]) && filters[key].length > 0) {
|
||||
filters[key].forEach((value, index) => {
|
||||
params[`${key}[${index}]`] = value;
|
||||
});
|
||||
} else if (!Array.isArray(filters[key]) && filters[key]) {
|
||||
params[key] = filters[key];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
},
|
||||
|
||||
},
|
||||
parseStringToArray(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter(item => item !== '' && item != null);
|
||||
}
|
||||
if (value && typeof value === 'string' && value.trim() !== '') {
|
||||
return value.split(',').map(item => item.trim()).filter(item => item !== '');
|
||||
}
|
||||
return [];
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
export const useGlobalStore = defineStore('GlobalStore', {
|
||||
state: () => ({
|
||||
current_user: {},
|
||||
sidebarOpen: false
|
||||
}),
|
||||
actions: {
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
export const useMediumStore = defineStore('MediumStore', {
|
||||
state: () => ({
|
||||
mediums: [],
|
||||
loader: false,
|
||||
selected: null,
|
||||
paginate: {
|
||||
total: null,
|
||||
current_page: null,
|
||||
last_page: null,
|
||||
links: null,
|
||||
per_page: null,
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,164 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
export const usePermissionStore = defineStore('permissionStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
datetime: '',
|
||||
name : '',
|
||||
label : '',
|
||||
parent_id : '',
|
||||
},
|
||||
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'شناسه',
|
||||
style: '!font-bold !text-sm',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'نام مقام',
|
||||
},
|
||||
{
|
||||
key: 'label',
|
||||
title: 'نام فارسی',
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
title: 'تاریخ ایجاد مقام',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
width: 120
|
||||
},
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/permissions',
|
||||
create: '/admin/permissions/create',
|
||||
store: '/admin/permissions',
|
||||
edit: (id) => `/admin/permissions/${id}/edit`,
|
||||
update: (id) => `/admin/permissions/${id}`,
|
||||
delete: (id) => `/admin/permissions/${id}`,
|
||||
bulkDelete: '/admin/permissions-bulk-delete',
|
||||
export: '/admin/permissions-export-data'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش دسترسی' : 'ثبت و ایجاد دسترسی جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش دسترسی' : 'افزودن دسترسی جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه دسترسی ها';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
update_and_store() {
|
||||
const url = this.is_edit_id ?
|
||||
this.endpoints.update(this.is_edit_id) :
|
||||
this.endpoints.store;
|
||||
const method = this.is_edit_id ? 'patch' : 'post';
|
||||
|
||||
this.loaderCreateForm = true;
|
||||
axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: this.createForm
|
||||
}).then(res => {
|
||||
this.errorsCreateForm = []
|
||||
this.loaderCreateForm = false;
|
||||
if (this.is_edit_id) {
|
||||
Inertia.get(this.endpoints.edit(this.is_edit_id));
|
||||
} else {
|
||||
this.resetCreateForm();
|
||||
Inertia.get(this.endpoints.create);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
datetime: '',
|
||||
name : '',
|
||||
label : '',
|
||||
parent_id : '',
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'image' && data.profile_url) {
|
||||
this.createForm[field] = { url: data.profile_url };
|
||||
} else if (field === 'datetime' && data.created_at) {
|
||||
this.createForm[field] = data.created_at;
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,286 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export const usePetStore = defineStore('PetStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
name: '',
|
||||
type: '',
|
||||
gender: '',
|
||||
breed: '',
|
||||
weight: '',
|
||||
sterilized: '',
|
||||
birthday: '',
|
||||
owner_id: '',
|
||||
},
|
||||
|
||||
typeOptions: [
|
||||
{ label: 'سگ', value: 'سگ' },
|
||||
{ label: 'گربه', value: 'گربه' },
|
||||
{ label: 'پرنده', value: 'پرنده' },
|
||||
{ label: 'خرگوش', value: 'خرگوش' },
|
||||
{ label: 'همستر', value: 'همستر' },
|
||||
{ label: 'خوکچه هندی', value: 'خوکچه هندی' },
|
||||
{ label: 'لاک پشت', value: 'لاک پشت' },
|
||||
{ label: 'مار', value: 'مار' },
|
||||
{ label: 'مارمولک', value: 'مارمولک' },
|
||||
{ label: 'سایر', value: 'سایر' },
|
||||
],
|
||||
genderOptions: [
|
||||
{ label: 'نر', value: 'نر' },
|
||||
{ label: 'ماده', value: 'ماده' },
|
||||
{ label: 'نامشخص', value: 'نامشخص' },
|
||||
],
|
||||
sterilizedOptions: [
|
||||
{ label: 'عقیم شده', value: true },
|
||||
{ label: 'عقیم نشده', value: false },
|
||||
],
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
filters: {},
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'نام',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
title: 'نوع',
|
||||
filter: {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
{ label: 'سگ', value: 'سگ' },
|
||||
{ label: 'گربه', value: 'گربه' },
|
||||
{ label: 'پرنده', value: 'پرنده' },
|
||||
{ label: 'خرگوش', value: 'خرگوش' },
|
||||
{ label: 'همستر', value: 'همستر' },
|
||||
{ label: 'خوکچه هندی', value: 'خوکچه هندی' },
|
||||
{ label: 'لاک پشت', value: 'لاک پشت' },
|
||||
{ label: 'مار', value: 'مار' },
|
||||
{ label: 'مارمولک', value: 'مارمولک' },
|
||||
{ label: 'سایر', value: 'سایر' },
|
||||
],
|
||||
placeholder: 'انتخاب نوع...'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'gender',
|
||||
title: 'جنسیت',
|
||||
slot: 'gender',
|
||||
filter: {
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'نر', value: 'نر' },
|
||||
{ label: 'ماده', value: 'ماده' },
|
||||
{ label: 'نامشخص', value: 'نامشخص' },
|
||||
],
|
||||
placeholder: 'انتخاب جنسیت...'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'breed',
|
||||
title: 'نژاد',
|
||||
render: (data) => data.breed ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'weight',
|
||||
title: 'وزن',
|
||||
render: (data) => data.weight + ' کیلوگرم' ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'sterilized',
|
||||
title: 'عقیمسازی',
|
||||
slot: 'sterilized',
|
||||
render: (data) => this.getSterilized(data.sterilized),
|
||||
},
|
||||
{
|
||||
key: 'age',
|
||||
title: 'سن',
|
||||
hasSort: true,
|
||||
width: 140,
|
||||
render:(data) => data?.age ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'owner',
|
||||
title: 'صاحب',
|
||||
width: 200,
|
||||
slot: 'owner',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
},
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/pets',
|
||||
create: '/admin/pets/create',
|
||||
store: '/admin/pets',
|
||||
edit: (id) => `/admin/pets/${id}/edit`,
|
||||
update: (id) => `/admin/pets/${id}`,
|
||||
delete: (id) => `/admin/pets/${id}`,
|
||||
bulkDelete: '/admin/pets-bulk-delete',
|
||||
export: '/admin/pets-export-data'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش کیس' : 'ثبت و ایجاد کیس جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش کیس' : 'افزودن کیس جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه کیس ها';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
update_and_store() {
|
||||
const url = this.is_edit_id ?
|
||||
this.endpoints.update(this.is_edit_id) :
|
||||
this.endpoints.store;
|
||||
const method = this.is_edit_id ? 'patch' : 'post';
|
||||
this.loaderCreateForm = true;
|
||||
axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: this.createForm
|
||||
}).then(res => {
|
||||
this.errorsCreateForm = []
|
||||
this.loaderCreateForm = false;
|
||||
if (this.is_edit_id) {
|
||||
Inertia.get(this.endpoints.edit(this.is_edit_id));
|
||||
} else {
|
||||
this.resetCreateForm();
|
||||
Inertia.get(this.endpoints.create);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
name: '',
|
||||
type: '',
|
||||
gender: '',
|
||||
breed: '',
|
||||
weight: '',
|
||||
sterilized: '',
|
||||
birthday: '',
|
||||
owner_id: '',
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'sterilized') {
|
||||
this.createForm[field] = data.sterilized !== null && data.sterilized !== undefined ? data.sterilized : '';
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
},
|
||||
|
||||
getSterilized(sterilized) {
|
||||
const sterilizedMap = {
|
||||
true: 'info',
|
||||
false: 'secondary'
|
||||
};
|
||||
return sterilizedMap[sterilized] || 'secondary';
|
||||
},
|
||||
|
||||
updateFilters(newFilters) {
|
||||
this.filters = { ...newFilters };
|
||||
this.search({ ...this.filters });
|
||||
},
|
||||
|
||||
clearFilters() {
|
||||
this.filters = {};
|
||||
this.search();
|
||||
},
|
||||
|
||||
updateFilterValue(key, value) {
|
||||
this.filters[key] = value;
|
||||
},
|
||||
|
||||
applyFilters() {
|
||||
this.search({ ...this.filters });
|
||||
},
|
||||
|
||||
convertFiltersToArrayParams(filters) {
|
||||
const params = {};
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (Array.isArray(filters[key]) && filters[key].length > 0) {
|
||||
filters[key].forEach((value, index) => {
|
||||
params[`${key}[${index}]`] = value;
|
||||
});
|
||||
} else if (!Array.isArray(filters[key]) && filters[key]) {
|
||||
params[key] = filters[key];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
},
|
||||
parseStringToArray(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter(item => item !== '' && item != null);
|
||||
}
|
||||
if (value && typeof value === 'string' && value.trim() !== '') {
|
||||
return value.split(',').map(item => item.trim()).filter(item => item !== '');
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,199 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export const useReportStore = defineStore('ReportStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
document_id: '',
|
||||
doctor_id: '',
|
||||
medium_id: '',
|
||||
status: '',
|
||||
body: '',
|
||||
submitted_at: '',
|
||||
},
|
||||
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
showCreateModal: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'شناسه',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
key: 'document',
|
||||
title: 'شناسه پرونده',
|
||||
width: 200,
|
||||
render: (data) => `<pre dir="ltr" class="text-right">${data.document?.code}</pre>`,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: 'وضعیت',
|
||||
slot: 'status',
|
||||
},
|
||||
{
|
||||
key: 'doctor',
|
||||
title: 'پزشک',
|
||||
render: (data) => data.doctor?.fullname ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'submitted_at',
|
||||
title: 'تاریخ ثبت',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
render:(data) => data.submitted_at ?? 'ثبت نشده',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
},
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/reports',
|
||||
create: '/admin/reports/create',
|
||||
store: '/admin/reports',
|
||||
edit: (id) => `/admin/reports/${id}/edit`,
|
||||
update: (id) => `/admin/reports/${id}`,
|
||||
delete: (id) => `/admin/reports/${id}`,
|
||||
bulkDelete: '/admin/reports-bulk-delete',
|
||||
export: '/admin/reports-export-data'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش گزارش' : 'ثبت و ایجاد گزارش جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش گزارش' : 'افزودن گزارش جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه گزارش ها';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
async create(formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.loaderCreateForm = true;
|
||||
Inertia.post(this.endpoints.store, formData, {
|
||||
onSuccess: (page) => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = [];
|
||||
this.resetCreateForm();
|
||||
resolve(page);
|
||||
},
|
||||
onError: (errors) => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = errors;
|
||||
reject(errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
async update(id, formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.loaderCreateForm = true;
|
||||
Inertia.patch(this.endpoints.update(id), formData, {
|
||||
onSuccess: (page) => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = [];
|
||||
this.resetCreateForm();
|
||||
resolve(page);
|
||||
},
|
||||
onError: (errors) => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = errors;
|
||||
reject(errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
document_id: '',
|
||||
doctor_id: '',
|
||||
medium_id: '',
|
||||
status: '',
|
||||
body: '',
|
||||
submitted_at: null,
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
this.showCreateModal = false;
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'submitted_at' && data.submitted_at) {
|
||||
this.createForm[field] = data.submitted_at;
|
||||
} else if (field === 'created_at' && data.created_at) {
|
||||
this.createForm[field] = data.created_at;
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
},
|
||||
|
||||
getStatusSeverity(status) {
|
||||
const statusSeverities = {
|
||||
'ثبت شده': 'info',
|
||||
'در حال انجام': 'warning',
|
||||
'تکمیل شده': 'success',
|
||||
'رد شده': 'danger',
|
||||
'لغو شده': 'secondary'
|
||||
};
|
||||
return statusSeverities[status] || 'secondary';
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,164 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
export const useRoleStore = defineStore('roleStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
datetime: '',
|
||||
name : '',
|
||||
label : '',
|
||||
selectedPermissionIds : {},
|
||||
},
|
||||
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'شناسه',
|
||||
style: '!font-bold !text-sm',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'نام مقام',
|
||||
},
|
||||
{
|
||||
key: 'label',
|
||||
title: 'نام فارسی',
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
title: 'تاریخ ایجاد مقام',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
width: 120
|
||||
},
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/roles',
|
||||
create: '/admin/roles/create',
|
||||
store: '/admin/roles',
|
||||
edit: (id) => `/admin/roles/${id}/edit`,
|
||||
update: (id) => `/admin/roles/${id}`,
|
||||
delete: (id) => `/admin/roles/${id}`,
|
||||
bulkDelete: '/admin/roles-bulk-delete',
|
||||
export: '/admin/roles-export-data'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش مقام' : 'ثبت و ایجاد مقام جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش مقام' : 'افزودن مقام جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه مقام ها';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
update_and_store() {
|
||||
const url = this.is_edit_id ?
|
||||
this.endpoints.update(this.is_edit_id) :
|
||||
this.endpoints.store;
|
||||
const method = this.is_edit_id ? 'patch' : 'post';
|
||||
|
||||
this.loaderCreateForm = true;
|
||||
axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: this.createForm
|
||||
}).then(res => {
|
||||
this.errorsCreateForm = []
|
||||
this.loaderCreateForm = false;
|
||||
if (this.is_edit_id) {
|
||||
Inertia.get(this.endpoints.edit(this.is_edit_id));
|
||||
} else {
|
||||
this.resetCreateForm();
|
||||
Inertia.get(this.endpoints.create);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
datetime: '',
|
||||
name : '',
|
||||
label : '',
|
||||
selectedPermissionIds : {},
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'image' && data.profile_url) {
|
||||
this.createForm[field] = { url: data.profile_url };
|
||||
} else if (field === 'datetime' && data.created_at) {
|
||||
this.createForm[field] = data.created_at;
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,100 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export const useSessionStore = defineStore('SessionStore', {
|
||||
state: () => ({
|
||||
loader: false,
|
||||
bulkLoader: false,
|
||||
selectedSessions: [],
|
||||
sessions: [],
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'ایدی',
|
||||
style: '!font-medium !text-sm',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
title: 'آی پی دستگاه',
|
||||
style: '!font-medium !text-sm',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
key: 'user_agent',
|
||||
title: 'نام دستگاه',
|
||||
style: '!font-medium !text-sm',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
key: 'last_activity',
|
||||
title: 'آخرین فعالیت',
|
||||
style: '!font-medium !text-xs',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
width: 80,
|
||||
},
|
||||
],
|
||||
|
||||
endpoints: {
|
||||
index: '/admin/setting-profile/sessions',
|
||||
delete: (id) => `/admin/setting-profile/sessions/${id}`,
|
||||
bulkDelete: '/admin/setting-profile/sessions-bulk-delete',
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
indexPageTitle: () => {
|
||||
return 'نمایش نشستهای فعال';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
setSessions(sessions) {
|
||||
this.sessions = sessions;
|
||||
},
|
||||
|
||||
deleteSession(sessionId) {
|
||||
this.loader = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(sessionId))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
this.loader = false;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDeleteSessions(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setSelectedSessions(sessions) {
|
||||
this.selectedSessions = sessions;
|
||||
},
|
||||
|
||||
clearSelectedSessions() {
|
||||
this.selectedSessions = [];
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,197 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from "axios";
|
||||
import { Inertia } from '@inertiajs/inertia'
|
||||
|
||||
export const useUserStore = defineStore('UserStore', {
|
||||
state: () => ({
|
||||
createForm: {
|
||||
datetime: '',
|
||||
role: {},
|
||||
words: {},
|
||||
username: '',
|
||||
email: '',
|
||||
fullname: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
level: 'پزشک',
|
||||
image: '',
|
||||
password_confirmation: '',
|
||||
},
|
||||
|
||||
loaderCreateForm: false,
|
||||
is_edit_form: false,
|
||||
errorsCreateForm: [],
|
||||
is_edit_id: null,
|
||||
|
||||
columns: [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'شناسه',
|
||||
style: '!font-bold !text-sm',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
title: 'نام کاربری',
|
||||
},
|
||||
// {
|
||||
// key: 'email',
|
||||
// title: 'ایمیل',
|
||||
// },
|
||||
{
|
||||
key: 'phone',
|
||||
title: 'شماره تماس',
|
||||
render: (data) => `<pre dir="ltr" class="text-right">${data.phone}</pre>`,
|
||||
},
|
||||
{
|
||||
key: 'fullname',
|
||||
title: 'نام و نام خانوادگی',
|
||||
width: 200,
|
||||
render: (data) => data.fullname,
|
||||
},
|
||||
{
|
||||
key: 'level',
|
||||
title: 'سطح',
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
title: 'تاریخ عضویت',
|
||||
hasSort: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'عملیات',
|
||||
hasSort: false,
|
||||
slot: true,
|
||||
},
|
||||
],
|
||||
levelOptions: [
|
||||
'مدیر',
|
||||
'پزشک',
|
||||
'سرپرست',
|
||||
],
|
||||
endpoints: {
|
||||
index: '/admin/users',
|
||||
create: '/admin/users/create',
|
||||
store: '/admin/users',
|
||||
edit: (id) => `/admin/users/${id}/edit`,
|
||||
update: (id) => `/admin/users/${id}`,
|
||||
delete: (id) => `/admin/users/${id}`,
|
||||
bulkDelete: '/admin/users-bulk-delete',
|
||||
export: '/admin/users-export-data'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
buttonLabel: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش کاربر' : 'ثبت و ایجاد کاربر جدید';
|
||||
},
|
||||
pageTitle: (state) => {
|
||||
return state.is_edit_id ? 'ویرایش کاربر' : 'افزودن کاربر جدید';
|
||||
},
|
||||
indexPageTitle: (state) => {
|
||||
return 'نمایش همه کاربران';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
update_and_store() {
|
||||
const url = this.is_edit_id ?
|
||||
this.endpoints.update(this.is_edit_id) :
|
||||
this.endpoints.store;
|
||||
const method = this.is_edit_id ? 'patch' : 'post';
|
||||
|
||||
this.loaderCreateForm = true;
|
||||
axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: this.createForm
|
||||
}).then(res => {
|
||||
this.errorsCreateForm = []
|
||||
this.loaderCreateForm = false;
|
||||
if (this.is_edit_id) {
|
||||
Inertia.get(this.endpoints.edit(this.is_edit_id));
|
||||
} else {
|
||||
this.resetCreateForm();
|
||||
Inertia.get(this.endpoints.create);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loaderCreateForm = false;
|
||||
this.errorsCreateForm = error.response.data?.errors ?? [];
|
||||
});
|
||||
},
|
||||
|
||||
resetCreateForm() {
|
||||
this.createForm = {
|
||||
username: '',
|
||||
role: {},
|
||||
email: '',
|
||||
fullname: '',
|
||||
phone: '',
|
||||
image: '',
|
||||
password: '',
|
||||
level: 'پزشک',
|
||||
datetime: '',
|
||||
password_confirmation: '',
|
||||
}
|
||||
this.is_edit_id = null;
|
||||
this.errorsCreateForm = [];
|
||||
},
|
||||
|
||||
setEditData(data) {
|
||||
this.is_edit_id = data.id;
|
||||
|
||||
Object.keys(this.createForm).forEach(field => {
|
||||
if (field === 'image' && data.profile_url) {
|
||||
this.createForm[field] = { url: data.profile_url };
|
||||
} else if (field === 'datetime' && data.created_at) {
|
||||
this.createForm[field] = data.created_at;
|
||||
} else {
|
||||
this.createForm[field] = data[field] || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteRecord(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(this.endpoints.delete(id))
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bulkDelete(ids) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.endpoints.bulkDelete, { ids })
|
||||
.then(response => {
|
||||
Inertia.get(this.endpoints.index);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
exportData(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
window.open(`${this.endpoints.export}?${query}`, '_blank');
|
||||
},
|
||||
|
||||
search(params) {
|
||||
return Inertia.get(this.endpoints.index, params, {
|
||||
preserveState: false
|
||||
});
|
||||
},
|
||||
|
||||
navigateToPage(page) {
|
||||
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import { definePreset } from '@primeuix/themes';
|
||||
import Aura from '@primeuix/themes/aura';
|
||||
|
||||
const AuraCustomPreset = definePreset(Aura, {
|
||||
semantic: {
|
||||
primary: {
|
||||
50: '#e9edf3',
|
||||
100: '#c8d2e1',
|
||||
200: '#a2afc8',
|
||||
300: '#7286a5',
|
||||
400: '#455a79',
|
||||
500: '#051a3a',
|
||||
600: '#041634',
|
||||
700: '#03132c',
|
||||
800: '#020f25',
|
||||
900: '#020a1b',
|
||||
950: '#010510',
|
||||
},
|
||||
colorScheme: {
|
||||
light: {
|
||||
primary: {
|
||||
color: '{primary.500}',
|
||||
contrastColor: '#ffffff',
|
||||
hoverColor: '{primary.600}',
|
||||
activeColor: '{primary.700}',
|
||||
},
|
||||
highlight: {
|
||||
background: '{primary.50}',
|
||||
focusBackground: '{primary.100}',
|
||||
color: '{primary.700}',
|
||||
focusColor: '{primary.800}',
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
primary: {
|
||||
color: '{primary.400}',
|
||||
contrastColor: '{surface.900}',
|
||||
hoverColor: '{primary.300}',
|
||||
activeColor: '{primary.200}',
|
||||
},
|
||||
highlight: {
|
||||
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||
color: 'rgba(255,255,255,.87)',
|
||||
focusColor: 'rgba(255,255,255,.87)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default AuraCustomPreset;
|
||||
Reference in New Issue
Block a user