This commit is contained in:
Parsa Nazer
2025-02-22 20:11:23 +03:30
10 changed files with 188 additions and 37 deletions
+12 -13
View File
@@ -1,5 +1,4 @@
<script setup lang="ts">
// types
type Props = {
@@ -16,18 +15,14 @@ defineProps<Props>();
// state
const params : any = inject("params");
const params: any = inject("params");
const page = ref(params?.page ? Number(params.page) : 1);
// computed
// watch
watch(
() => page.value,
(newPage) => {
params.page = newPage;
}
);
const page = computed({
get: () => (params?.page ? Number(params.page) : 1),
set: (value: number) => (params.page = value),
});
</script>
<template>
@@ -47,7 +42,11 @@ watch(
<PaginationNext
class="w-9 h-9 ml-4 flex items-center justify-center cursor-pointer hover:bg-slate-100 transition disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
>
<Icon name="ci:chevron-right" class="**:fill-back" size="18px" />
<Icon
name="ci:chevron-right"
class="**:fill-back"
size="18px"
/>
</PaginationNext>
<template v-for="(page, index) in items">
@@ -81,4 +80,4 @@ watch(
</PaginationFirst>
</PaginationList>
</PaginationRoot>
</template>
</template>
+1 -1
View File
@@ -13,7 +13,7 @@ defineProps<Props>();
<template>
<div
class="w-full flex-col flex-grow py-[12rem] gap-6 border-2 border-slate-200 border-dashed size-full rounded-100 flex-center"
class="w-full flex-col flex-grow py-[12rem] gap-6 border-2 border-slate-200 border-dashed size-full rounded-xl flex-center"
>
<Icon :name="icon" size="50" class="**:fill-gray-500" />
<span class="text-lg text-gray-500"> {{ title }} </span>
+15 -5
View File
@@ -56,13 +56,23 @@ await suspense();
{{ `${account?.first_name} ${account?.last_name}` }}
</p>
</span>
<button class="text-xs font-semibold text-cyan-500">
<NuxtLink
:to="{ name: 'profile' }"
class="text-xs font-semibold text-cyan-500"
>
ویرایش اطلاعات
</button>
</NuxtLink>
</div>
<img
src="https://shatelpart.com/storage/users/user-15-155Mn1.png"
class="rounded-full size-[3rem] hover:border-dynamic-rose transition"
<Avatar
class="!size-[3rem]"
:src="account!.profile_photo"
:alt="
account?.first_name && account?.last_name
? `${account?.first_name.charAt(
0
)} ${account?.last_name.charAt(0)}`
: 'بدون نام کاربری'
"
/>
</div>
</div>
@@ -1,4 +1,14 @@
<script setup lang="ts"></script>
<script setup lang="ts">
// types
type Props = {
data: Ticket;
};
// props
defineProps<Props>();
</script>
<template>
<tr
@@ -8,13 +18,15 @@
scope="row"
class="w-3/12 px-6 py-6 font-medium whitespace-nowrap text-black"
>
Apple MacBook Pro 17
{{ data.ticket_category }}
</td>
<td class="w-3/12 px-6 py-6">Silver</td>
<td class="w-3/12 px-6 py-6">Laptop</td>
<td class="w-2/12 px-6 py-6">$2999</td>
<td class="w-3/12 px-6 py-6">{{ data.subject }}</td>
<td class="w-3/12 px-6 py-6">{{ data.created_at }}</td>
<td class="w-2/12 px-6 py-6">{{ data.status }}</td>
<td class="w-1/12 px-6 py-6">
<NuxtLink :to="{ name: 'profile-tickets-id', params: { id: 1 } }">
<NuxtLink
:to="{ name: 'profile-tickets-id', params: { id: data.id } }"
>
<button
class="size-10 flex-center border border-slate-200 rounded-md"
>
@@ -0,0 +1,28 @@
<script setup lang="ts"></script>
<template>
<tr
class="odd:bg-white even:bg-gray-50 last:border-none border-b border-slate-200"
>
<td
scope="row"
class="w-3/12 px-6 py-6 font-medium whitespace-nowrap text-black"
>
<Skeleton class="w-full !h-10 !rounded-sm" />
</td>
<td class="w-3/12 px-6 py-6">
<Skeleton class="w-full !h-10 !rounded-sm" />
</td>
<td class="w-3/12 px-6 py-6">
<Skeleton class="w-full !h-10 !rounded-sm" />
</td>
<td class="w-2/12 px-6 py-6">
<Skeleton class="w-full !h-10 !rounded-sm" />
</td>
<td class="w-1/12 px-6 py-6">
<Skeleton class="!size-10 !rounded-sm" />
</td>
</tr>
</template>
<style scoped></style>
+7 -6
View File
@@ -11,13 +11,11 @@ export type SignInRequest = {
};
export type SignInResponse = {
access: string,
refresh: string,
access: string;
refresh: string;
};
const useSignIn = () => {
// state
const { $axios: axios } = useNuxtApp();
@@ -25,12 +23,15 @@ const useSignIn = () => {
// methods
const handleSignIn = async (variables: SignInRequest) => {
const { data } = await axios.post<SignInResponse>(`${API_ENDPOINTS.auth.signin}/`, variables);
const { data } = await axios.post<SignInResponse>(
`${API_ENDPOINTS.auth.signin}`,
variables
);
return data;
};
return useMutation({
mutationFn: (variables: SignInRequest) => handleSignIn(variables)
mutationFn: (variables: SignInRequest) => handleSignIn(variables),
});
};
@@ -0,0 +1,44 @@
// imports
import { useQuery } from "@tanstack/vue-query";
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
// types
export type GetAllTicketsResponse = ApiPaginated<Ticket>;
export type GetAllTicketsFilters = {
sort: string | undefined;
filter: string | undefined;
page: string | string[];
};
const useGetAllTickets = (params: Ref<GetAllTicketsFilters>) => {
// state
const { $axios: axios } = useNuxtApp();
// methods
const handleGetAllTickets = async (params: GetAllTicketsFilters) => {
const { data } = await axios.get<GetAllTicketsResponse>(
API_ENDPOINTS.tickets.get_all,
{
params: {
sort: params.sort,
filter: params.filter,
offset: Number(params.page) * 10 - 10,
limit: 10,
},
}
);
return data;
};
return useQuery({
queryKey: [QUERY_KEYS.tickets, params],
queryFn: () => handleGetAllTickets(params.value),
});
};
export default useGetAllTickets;
+4
View File
@@ -34,6 +34,9 @@ export const API_ENDPOINTS = {
get_all: "/products",
categories: "/products/categories",
},
tickets: {
get_all: "/tickets/",
},
};
export const QUERY_KEYS = {
@@ -47,6 +50,7 @@ export const QUERY_KEYS = {
account: "account",
categories: "categories",
addresses: "addresses",
tickets: "tickets",
};
export const MUTATION_KEYS = {
+50 -6
View File
@@ -1,4 +1,10 @@
<script setup lang="ts">
// imports
import useGetAllTickets, {
type GetAllTicketsFilters,
} from "~/composables/api/tickets/useGetAllTickets";
// meta
definePageMeta({
@@ -8,10 +14,12 @@ definePageMeta({
// state
const filters = ref({
const params = useUrlSearchParams();
const filters = ref<GetAllTicketsFilters>({
sort: undefined,
status: undefined,
search: "",
filter: undefined,
page: params.page ?? 1,
});
const tableHeads = ref([
@@ -21,6 +29,22 @@ const tableHeads = ref([
"وضعیت",
"عملیات",
]);
// queries
const { data, isLoading: ticketsIsLoading } = useGetAllTickets(filters);
// computed
const tickets = computed(() => {
return data.value?.results.flat();
});
const paginationData = computed(() => {
return tickets!.value?.results.map((_, i: number) => {
return { type: "page", value: i };
});
});
</script>
<template>
@@ -47,7 +71,7 @@ const tableHeads = ref([
'در حال پردازش',
'لغو شده',
]"
v-model="filters.status!"
v-model="filters.filter!"
triggerRootClass="!py-2.5"
class="w-[5rem]"
/>
@@ -63,7 +87,14 @@ const tableHeads = ref([
</NuxtLink>
</div>
<Table>
<Placeholder
v-if="!tickets?.length && !ticketsIsLoading"
class="!w-full !py-[5rem]"
icon="bi:ticket"
title="تیکتی یافت نشد"
/>
<Table v-else>
<template #thead>
<th
v-for="(tableHead, index) in tableHeads"
@@ -82,9 +113,22 @@ const tableHeads = ref([
</th>
</template>
<template #tbody>
<TicketsTableRow v-for="i in 4" />
<template v-if="ticketsIsLoading">
<TicketsTableRowLoading v-for="i in 5" />
</template>
<template v-else>
<TicketsTableRow
v-for="(ticket, index) in tickets"
:key="index"
:data="ticket"
/>
</template>
</template>
</Table>
<div v-if="tickets?.length > 10" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data?.count" />
</div>
</div>
</div>
</template>
+9
View File
@@ -165,4 +165,13 @@ declare global {
iconClass?: string;
onClick?: () => void;
};
type Ticket = {
id: number;
subject: string;
ticket_category: string;
status: string;
created_at: string;
updated_at: string;
};
}