update frontend
This commit is contained in:
@@ -25,7 +25,7 @@ const { date } = toRefs(props);
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
const createdAt = usePersianTimeAgo(new Date(date.value));
|
const createdAt = usePersianTimeAgo(date.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// imports
|
// imports
|
||||||
|
|
||||||
import useDownloadInvoice from "~/composables/api/orders/useDownloadInvoice";
|
import useDownloadInvoice from "~/composables/api/orders/useDownloadInvoice";
|
||||||
|
import usePersianDate from "~/composables/global/usePersianDate";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
@@ -15,6 +16,10 @@ const props = defineProps<Props>();
|
|||||||
|
|
||||||
const { data } = toRefs(props);
|
const { data } = toRefs(props);
|
||||||
|
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { formatToPersian } = usePersianDate();
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
const { downloadFn, downloadIsLoading } = useDownloadInvoice(String(data.value.id));
|
const { downloadFn, downloadIsLoading } = useDownloadInvoice(String(data.value.id));
|
||||||
@@ -30,7 +35,7 @@ const { downloadFn, downloadIsLoading } = useDownloadInvoice(String(data.value.i
|
|||||||
{{ data.order_id ? `${data.order_id}#` : "--" }}
|
{{ data.order_id ? `${data.order_id}#` : "--" }}
|
||||||
</td>
|
</td>
|
||||||
<td class="w-3/12 px-6 py-6 text-xs lg:text-sm font-medium whitespace-pre shrink-0">
|
<td class="w-3/12 px-6 py-6 text-xs lg:text-sm font-medium whitespace-pre shrink-0">
|
||||||
{{ data.created_at ?? "--" }}
|
{{ data.created_at ? formatToPersian(data.created_at) : "--" }}
|
||||||
</td>
|
</td>
|
||||||
<td class="w-2/12 px-6 py-6 text-xs lg:text-sm whitespace-pre shrink-0">
|
<td class="w-2/12 px-6 py-6 text-xs lg:text-sm whitespace-pre shrink-0">
|
||||||
{{ data.count ? data.count : "--" }}
|
{{ data.count ? data.count : "--" }}
|
||||||
@@ -53,24 +58,40 @@ const { downloadFn, downloadIsLoading } = useDownloadInvoice(String(data.value.i
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="w-1/12 px-6 py-6 shrink-0">
|
<td class="w-1/12 px-6 py-6 shrink-0">
|
||||||
<button
|
<div class="flex items-center gap-2">
|
||||||
@click="!downloadIsLoading ? downloadFn() : undefined"
|
<NuxtLink :to="{ name: 'profile-purchases-and-orders-id', params: { id: data.id } }">
|
||||||
:disabled="downloadIsLoading"
|
<button
|
||||||
class="size-9 lg:size-10 flex-center border border-slate-200 rounded-md"
|
class="size-9 lg:size-10 flex-center border border-slate-200 rounded-md"
|
||||||
>
|
aria-label="مشاهده جزئیات سفارش"
|
||||||
<Icon
|
>
|
||||||
v-if="downloadIsLoading"
|
<Icon
|
||||||
name="ci:svg-spinners-3-dots-fade"
|
name="ci:eye-open"
|
||||||
class="**:stroke-black"
|
class="**:stroke-black"
|
||||||
size="20"
|
size="20"
|
||||||
/>
|
/>
|
||||||
<Icon
|
</button>
|
||||||
v-else
|
</NuxtLink>
|
||||||
name="ci:bi-download"
|
<button
|
||||||
class="**:stroke-black"
|
v-if="data.is_paid"
|
||||||
size="20"
|
@click="!downloadIsLoading ? downloadFn() : undefined"
|
||||||
/>
|
:disabled="downloadIsLoading"
|
||||||
</button>
|
class="size-9 lg:size-10 flex-center border border-slate-200 rounded-md"
|
||||||
|
aria-label="دانلود فاکتور"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="downloadIsLoading"
|
||||||
|
name="ci:svg-spinners-3-dots-fade"
|
||||||
|
class="**:stroke-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
name="ci:bi-download"
|
||||||
|
class="**:stroke-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -23,7 +23,10 @@
|
|||||||
<Skeleton class="w-full !h-10 !rounded-sm" />
|
<Skeleton class="w-full !h-10 !rounded-sm" />
|
||||||
</td>
|
</td>
|
||||||
<td class="w-1/12 px-6 py-6 shrink-0">
|
<td class="w-1/12 px-6 py-6 shrink-0">
|
||||||
<Skeleton class="!size-10 !rounded-sm" />
|
<div class="flex items-center gap-2">
|
||||||
|
<Skeleton class="!size-10 !rounded-sm" />
|
||||||
|
<Skeleton class="!size-10 !rounded-sm" />
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const { is_user, files, date } = toRefs(props);
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
const timeAgo = usePersianTimeAgo(new Date(date.value));
|
const timeAgo = usePersianTimeAgo(date.value);
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const { data } = toRefs(props);
|
|||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
|
||||||
const createdTimeAgo = usePersianTimeAgo(new Date(data.value.created_at));
|
const createdTimeAgo = usePersianTimeAgo(data.value.created_at);
|
||||||
const updatedTimeAgo = usePersianTimeAgo(new Date(data.value.updated_at));
|
const updatedTimeAgo = usePersianTimeAgo(data.value.updated_at);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const useGetAllOrders = () => {
|
|||||||
const handleGetAllOrders = async () => {
|
const handleGetAllOrders = async () => {
|
||||||
const { data } = await axios.get<GetAllOrdersResponse>(API_ENDPOINTS.orders.get_all, {
|
const { data } = await axios.get<GetAllOrdersResponse>(API_ENDPOINTS.orders.get_all, {
|
||||||
params: {
|
params: {
|
||||||
sort: sort.value ?? "created_at",
|
sort: sort.value ?? "-created_at",
|
||||||
status: status.value,
|
status: status.value,
|
||||||
offset: Number(page.value) * 10 - 10,
|
offset: Number(page.value) * 10 - 10,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// imports
|
||||||
|
|
||||||
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
|
import type { ComputedRef } from "vue";
|
||||||
|
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||||
|
|
||||||
|
// types
|
||||||
|
|
||||||
|
export type GetOrderResponse = OrderDetail;
|
||||||
|
|
||||||
|
const useGetOrder = (id: ComputedRef<string>) => {
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { $axios: axios } = useNuxtApp();
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
const handleGetOrder = async () => {
|
||||||
|
const { data } = await axios.get<GetOrderResponse>(`${API_ENDPOINTS.orders.get_one}/${id.value}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [QUERY_KEYS.order, id],
|
||||||
|
queryFn: () => handleGetOrder(),
|
||||||
|
enabled: computed(() => !!id.value),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGetOrder;
|
||||||
@@ -26,7 +26,7 @@ const useGetAllTickets = () => {
|
|||||||
const handleGetAllTickets = async () => {
|
const handleGetAllTickets = async () => {
|
||||||
const { data } = await axios.get<GetAllTicketsResponse>(API_ENDPOINTS.tickets.get_all, {
|
const { data } = await axios.get<GetAllTicketsResponse>(API_ENDPOINTS.tickets.get_all, {
|
||||||
params: {
|
params: {
|
||||||
sort: sort.value ?? "created_at",
|
sort: sort.value ?? "-created_at",
|
||||||
filter: status.value,
|
filter: status.value,
|
||||||
offset: Number(page.value) * 7 - 7,
|
offset: Number(page.value) * 7 - 7,
|
||||||
limit: 7,
|
limit: 7,
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
// composables/usePersianDate.ts
|
// composables/usePersianDate.ts
|
||||||
import { format, toDate } from "date-fns-jalali";
|
import { format } from "date-fns-jalali";
|
||||||
import { faIR } from "date-fns-jalali/locale";
|
import { faIR } from "date-fns-jalali/locale";
|
||||||
|
import { Jalali } from "jalali-ts";
|
||||||
|
|
||||||
export default function usePersianDate() {
|
export default function usePersianDate() {
|
||||||
const formatToPersian = (isoDate: string): string => {
|
const formatToPersian = (isoDate: string): string => {
|
||||||
try {
|
try {
|
||||||
const date = toDate(new Date(isoDate));
|
const yearStr = isoDate.slice(0, 4);
|
||||||
|
const year = Number(yearStr);
|
||||||
|
|
||||||
|
// jDateTimeField from django_jalali sends Jalali years (13XX/14XX).
|
||||||
|
// Normal DateTimeField sends Gregorian (19XX/20XX). Detect by year
|
||||||
|
// and use jalali-ts to convert when needed — date-fns-jalali's
|
||||||
|
// parseISO doesn't actually do calendar conversion.
|
||||||
|
const date =
|
||||||
|
!Number.isNaN(year) && year < 1700
|
||||||
|
? Jalali.parse(isoDate).date
|
||||||
|
: new Date(isoDate);
|
||||||
|
|
||||||
|
if (isNaN(date.getTime())) return "Invalid date";
|
||||||
|
|
||||||
const persianDate = format(date, "yyyy/MM/dd", { locale: faIR });
|
const persianDate = format(date, "yyyy/MM/dd", { locale: faIR });
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,44 @@
|
|||||||
// composables/usePersianTimeAgo.ts
|
// composables/usePersianTimeAgo.ts
|
||||||
import { formatDistance, toDate } from "date-fns-jalali";
|
import { formatDistance } from "date-fns-jalali";
|
||||||
import { faIR } from "date-fns-jalali/locale";
|
import { faIR } from "date-fns-jalali/locale";
|
||||||
|
import { Jalali } from "jalali-ts";
|
||||||
|
|
||||||
export function usePersianTimeAgo(date: Date) {
|
const toGregorianDate = (input: Date | string): Date | null => {
|
||||||
|
if (input instanceof Date) {
|
||||||
|
return isNaN(input.getTime()) ? null : input;
|
||||||
|
}
|
||||||
|
if (typeof input !== "string" || !input) return null;
|
||||||
|
|
||||||
|
const yearStr = input.slice(0, 4);
|
||||||
|
const year = Number(yearStr);
|
||||||
|
|
||||||
|
// jDateTimeField from django_jalali serializes with the Jalali year
|
||||||
|
// (typically 13XX or 14XX). Anything below ~1700 is treated as Jalali
|
||||||
|
// and converted to the equivalent Gregorian moment via jalali-ts.
|
||||||
|
// date-fns-jalali's parseISO can't be used here — it parses the year
|
||||||
|
// numerically without calendar conversion.
|
||||||
|
if (!Number.isNaN(year) && year < 1700) {
|
||||||
|
try {
|
||||||
|
return Jalali.parse(input).date;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const native = new Date(input);
|
||||||
|
return isNaN(native.getTime()) ? null : native;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function usePersianTimeAgo(date: Date | string) {
|
||||||
const timeAgo = ref("");
|
const timeAgo = ref("");
|
||||||
|
|
||||||
const updateTimeAgo = () => {
|
const updateTimeAgo = () => {
|
||||||
timeAgo.value = formatDistance(toDate(date), new Date(), {
|
const parsed = toGregorianDate(date);
|
||||||
|
if (!parsed) {
|
||||||
|
timeAgo.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timeAgo.value = formatDistance(parsed, new Date(), {
|
||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
locale: faIR,
|
locale: faIR,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export const API_ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
orders: {
|
orders: {
|
||||||
get_all: "/order/all",
|
get_all: "/order/all",
|
||||||
|
get_one: "/order",
|
||||||
cart: {
|
cart: {
|
||||||
download_invoice: "/order/invoice",
|
download_invoice: "/order/invoice",
|
||||||
get_all: "/order/cart",
|
get_all: "/order/cart",
|
||||||
@@ -94,6 +95,7 @@ export const QUERY_KEYS = {
|
|||||||
tickets: "tickets",
|
tickets: "tickets",
|
||||||
ticket: "ticket",
|
ticket: "ticket",
|
||||||
orders: "orders",
|
orders: "orders",
|
||||||
|
order: "order",
|
||||||
cart: "cart",
|
cart: "cart",
|
||||||
transaction: "transaction",
|
transaction: "transaction",
|
||||||
notifications: "notifications",
|
notifications: "notifications",
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import useGetOrder from "~/composables/api/orders/useGetOrder";
|
||||||
|
import useDownloadInvoice from "~/composables/api/orders/useDownloadInvoice";
|
||||||
|
import usePersianDate from "~/composables/global/usePersianDate";
|
||||||
|
|
||||||
|
// meta
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: "پنل کاربری جزئیات سفارش",
|
||||||
|
});
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "check-is-logged-in",
|
||||||
|
layout: "profile",
|
||||||
|
});
|
||||||
|
|
||||||
|
// state
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { formatToPersian } = usePersianDate();
|
||||||
|
|
||||||
|
// computed
|
||||||
|
|
||||||
|
const orderId = computed(() => route.params.id as string);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
|
||||||
|
const { data: order, isLoading: orderIsLoading } = useGetOrder(orderId);
|
||||||
|
|
||||||
|
const { downloadFn, downloadIsLoading } = useDownloadInvoice(orderId.value);
|
||||||
|
|
||||||
|
// computed
|
||||||
|
|
||||||
|
const statusVariant = computed(() => {
|
||||||
|
const status = order.value?.status;
|
||||||
|
if (!status) return "neutral";
|
||||||
|
if (["ADMIN_PENDING", "PENDING"].includes(status)) return "warning";
|
||||||
|
if (["POSTED", "RECEIVED"].includes(status)) return "success";
|
||||||
|
if (["CANCELED", "REFUND", "REFUNDED"].includes(status)) return "danger";
|
||||||
|
return "neutral";
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusClass = computed(() => {
|
||||||
|
return {
|
||||||
|
warning: "text-warning-600 bg-warning-100 border-warning-600",
|
||||||
|
success: "text-success-600 bg-success-100 border-success-600",
|
||||||
|
danger: "text-danger-600 bg-danger-100 border-danger-600",
|
||||||
|
neutral: "text-slate-600 bg-slate-100 border-slate-300",
|
||||||
|
}[statusVariant.value];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full flex flex-col gap-5">
|
||||||
|
<ProfilePageTitle
|
||||||
|
:title="`جزئیات سفارش ${order?.order_id ? `${order.order_id}#` : ''}`"
|
||||||
|
icon="ci:bi-cart"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-5 lg:px-5">
|
||||||
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||||
|
<NuxtLink :to="{ name: 'profile-purchases-and-orders' }">
|
||||||
|
<Button
|
||||||
|
end-icon="ci:bi-arrow-left"
|
||||||
|
size="md"
|
||||||
|
class="rounded-full"
|
||||||
|
>
|
||||||
|
<span class="whitespace-pre">بازگشت به سفارشات</span>
|
||||||
|
</Button>
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
v-if="order?.is_paid"
|
||||||
|
@click="!downloadIsLoading ? downloadFn() : undefined"
|
||||||
|
:disabled="downloadIsLoading"
|
||||||
|
end-icon="ci:bi-download"
|
||||||
|
size="md"
|
||||||
|
class="rounded-full"
|
||||||
|
>
|
||||||
|
<span class="whitespace-pre">دانلود فاکتور</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="orderIsLoading"
|
||||||
|
class="w-full grid grid-cols-1 lg:grid-cols-3 gap-5"
|
||||||
|
>
|
||||||
|
<Skeleton class="!w-full !h-40 !rounded-xl lg:col-span-2" />
|
||||||
|
<Skeleton class="!w-full !h-40 !rounded-xl" />
|
||||||
|
<Skeleton class="!w-full !h-60 !rounded-xl lg:col-span-2" />
|
||||||
|
<Skeleton class="!w-full !h-60 !rounded-xl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Placeholder
|
||||||
|
v-else-if="!order"
|
||||||
|
class="!w-full !py-[5rem]"
|
||||||
|
icon="ci:bi-cart"
|
||||||
|
title="سفارشی یافت نشد"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="w-full grid grid-cols-1 lg:grid-cols-3 gap-5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="lg:col-span-2 w-full flex flex-col gap-4 p-5 border border-slate-200 rounded-xl bg-slate-50"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon
|
||||||
|
name="ci:bi-cart"
|
||||||
|
class="**:fill-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold">اطلاعات سفارش</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="rounded-full py-1.5 px-3 text-xs border"
|
||||||
|
:class="statusClass"
|
||||||
|
>
|
||||||
|
{{ order.verbose_status ?? "--" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs lg:text-sm">
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">شماره سفارش:</span>
|
||||||
|
<span class="font-medium text-cyan-600">{{ order.order_id ? `${order.order_id}#` : "--" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">تاریخ ثبت:</span>
|
||||||
|
<span class="font-medium">
|
||||||
|
{{ order.created_at ? formatToPersian(order.created_at) : "--" }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">تعداد اقلام:</span>
|
||||||
|
<span class="font-medium">{{ order.count ?? "--" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">وضعیت پرداخت:</span>
|
||||||
|
<span class="font-medium">
|
||||||
|
{{ order.is_paid ? "پرداخت شده" : "پرداخت نشده" }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full flex flex-col gap-4 p-5 border border-slate-200 rounded-xl bg-slate-50"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon
|
||||||
|
name="ci:bi-map"
|
||||||
|
class="**:fill-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold">آدرس تحویل</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="order.address"
|
||||||
|
class="flex flex-col gap-2 text-xs lg:text-sm"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">گیرنده:</span>
|
||||||
|
<span class="font-medium">{{ order.address.name ?? "--" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">شماره تماس:</span>
|
||||||
|
<span class="font-medium">{{ order.address.phone ?? "--" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">استان / شهر:</span>
|
||||||
|
<span class="font-medium">
|
||||||
|
{{ order.address.province ?? "--" }} / {{ order.address.city ?? "--" }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="text-dynamic-secondary">کد پستی:</span>
|
||||||
|
<span class="font-medium">{{ order.address.postal_code ?? "--" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-dynamic-secondary leading-[180%]">
|
||||||
|
{{ order.address.address ?? "--" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="text-xs text-dynamic-secondary"
|
||||||
|
>
|
||||||
|
آدرسی ثبت نشده است
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="lg:col-span-2 w-full flex flex-col gap-4 p-5 border border-slate-200 rounded-xl bg-slate-50"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon
|
||||||
|
name="ci:bi-box"
|
||||||
|
class="**:fill-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold">اقلام سفارش</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Placeholder
|
||||||
|
v-if="!order.items?.length"
|
||||||
|
class="!w-full !py-10"
|
||||||
|
icon="ci:bi-box"
|
||||||
|
title="کالایی در این سفارش ثبت نشده"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-else
|
||||||
|
class="w-full flex flex-col divide-y divide-slate-200"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="item in order.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="w-full flex items-center gap-3 py-4"
|
||||||
|
>
|
||||||
|
<NuxtImg
|
||||||
|
v-if="item.product?.image"
|
||||||
|
:src="item.product.image"
|
||||||
|
loading="lazy"
|
||||||
|
fetch-priority="low"
|
||||||
|
class="size-16 lg:size-20 rounded-100 border border-slate-200 object-cover"
|
||||||
|
/>
|
||||||
|
<div class="flex-1 flex flex-col gap-1">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="item.product?.slug"
|
||||||
|
:to="`/product/${item.product.slug}`"
|
||||||
|
class="text-xs lg:text-sm font-semibold line-clamp-2 hover:text-cyan-600 transition-colors"
|
||||||
|
>
|
||||||
|
{{ item.product.title ?? "--" }}
|
||||||
|
</NuxtLink>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-xs lg:text-sm font-semibold line-clamp-2"
|
||||||
|
>
|
||||||
|
{{ item.product?.title ?? "--" }}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-dynamic-secondary">
|
||||||
|
تعداد: {{ item.quantity }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-end gap-1 text-xs lg:text-sm">
|
||||||
|
<span class="font-medium whitespace-pre">{{ item.final_price }}</span>
|
||||||
|
<span
|
||||||
|
v-if="item.discount_percent && item.discount_percent > 0"
|
||||||
|
class="text-dynamic-secondary line-through whitespace-pre"
|
||||||
|
>
|
||||||
|
{{ item.price }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full flex flex-col gap-4 p-5 border border-slate-200 rounded-xl bg-slate-50 h-fit"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon
|
||||||
|
name="ci:bi-tag"
|
||||||
|
class="**:fill-black"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold">خلاصه فاکتور</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3 text-xs lg:text-sm">
|
||||||
|
<div
|
||||||
|
v-if="order.cart_total"
|
||||||
|
class="flex items-center justify-between gap-2 text-slate-800"
|
||||||
|
>
|
||||||
|
<span>جمع سبد:</span>
|
||||||
|
<span>{{ order.cart_total }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="order.discount_amount"
|
||||||
|
class="flex items-center justify-between gap-2 text-red-700"
|
||||||
|
>
|
||||||
|
<span>تخفیف:</span>
|
||||||
|
<span>{{ order.discount_amount }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="order.special_discount_total"
|
||||||
|
class="flex items-center justify-between gap-2 text-green-700"
|
||||||
|
>
|
||||||
|
<span>تخفیف ویژه:</span>
|
||||||
|
<span>{{ order.special_discount_total }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="order.tax"
|
||||||
|
class="flex items-center justify-between gap-2 text-slate-800"
|
||||||
|
>
|
||||||
|
<span>مالیات:</span>
|
||||||
|
<span>{{ order.tax }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between gap-2 pt-3 border-t border-slate-200 text-slate-900 font-semibold"
|
||||||
|
>
|
||||||
|
<span>مبلغ نهایی:</span>
|
||||||
|
<span>{{ order.final_price ?? "--" }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -26,11 +26,11 @@ const tableHeads = ref(["دسته بندی", "موضوع", "تاریخ ایجا
|
|||||||
const sortFilters = ref([
|
const sortFilters = ref([
|
||||||
{
|
{
|
||||||
title: "جدید ترین",
|
title: "جدید ترین",
|
||||||
value: "created_at",
|
value: "-created_at",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "قدیمی ترین",
|
title: "قدیمی ترین",
|
||||||
value: "-created_at",
|
value: "created_at",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -76,8 +76,8 @@ const paginationData = computed(() => {
|
|||||||
// methods
|
// methods
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
sort.value = "";
|
sort.value = undefined;
|
||||||
status.value = "";
|
status.value = undefined;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Vendored
+21
@@ -223,6 +223,27 @@ declare global {
|
|||||||
special_discount_total?: string;
|
special_discount_total?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OrderDetailItem = {
|
||||||
|
id: number;
|
||||||
|
product: CartItem["product"];
|
||||||
|
quantity: number;
|
||||||
|
price: string;
|
||||||
|
final_price: string;
|
||||||
|
discount: number;
|
||||||
|
discount_amount: string;
|
||||||
|
special_discount_amount: string | null;
|
||||||
|
discount_percent?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OrderDetail = Order & {
|
||||||
|
items: OrderDetailItem[];
|
||||||
|
address: Address | null;
|
||||||
|
tax: number | string | null;
|
||||||
|
cart_total: number | string | null;
|
||||||
|
discount_code: DiscountCode | null;
|
||||||
|
discount_amount: number | string | null;
|
||||||
|
};
|
||||||
|
|
||||||
type DiscountCode = {
|
type DiscountCode = {
|
||||||
code: string;
|
code: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user