vercel deploy

This commit is contained in:
Samandar Turgunboyev
2025-12-23 10:01:09 +05:00
parent 2d4d91e163
commit c9cf9bb725
11 changed files with 780 additions and 26 deletions

View File

@@ -39,7 +39,6 @@ const AuthLogin = () => {
richColors: true,
position: "top-center",
});
console.log(res.data.data.access);
saveToken(res.data.data.access);
navigate("dashboard");

View File

@@ -0,0 +1,30 @@
import type { OrdersResponse } from "@/features/order/lib/type";
import httpClient from "@/shared/config/api/httpClient";
import { API_URLS } from "@/shared/config/api/URLs";
import type { AxiosResponse } from "axios";
export const order_api = {
async list(params: {
page: number;
page_size: number;
}): Promise<AxiosResponse<OrdersResponse>> {
const res = await httpClient.get(`${API_URLS.OrdersList}`, { params });
return res;
},
async status_update({
body,
id,
}: {
id: string | number;
body: { status: string };
}) {
const res = await httpClient.post(API_URLS.OrderStatus(id), body);
return res;
},
async delete_order(id: number | string) {
const res = await httpClient.delete(API_URLS.OrdersDelete(id));
return res;
},
};

View File

@@ -0,0 +1,50 @@
export interface User {
id: string;
first_name: string;
last_name: string;
username: string;
}
export interface Product {
id: string;
name_uz: string;
name_ru: string;
price: number;
code: string | null;
unity: string;
}
export interface Item {
id: string;
quantity: number;
price: number;
product: Product;
}
export interface Order {
id: string;
order_number: number;
status: string;
total_price: number;
user: User;
payment_type: string;
delivery_type: string;
delivery_price: number;
contact_number: string;
comment: string;
name: string;
items: Item[];
created_at: string;
long: number | null;
lat: number | null;
}
export interface OrdersResponse {
total: number;
page: number;
page_size: number;
total_pages: number;
has_next: boolean;
has_previous: boolean;
results: Order[];
}

View File

@@ -0,0 +1,89 @@
import { order_api } from "@/features/order/lib/api";
import type { Order } from "@/features/order/lib/type";
import { Button } from "@/shared/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { Loader2, Trash, X } from "lucide-react";
import type { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";
interface Props {
opneDelete: boolean;
setOpenDelete: Dispatch<SetStateAction<boolean>>;
setOrderDelete: Dispatch<SetStateAction<Order | null>>;
orderDelete: Order | null;
}
const OrderDelete = ({
opneDelete,
orderDelete,
setOpenDelete,
setOrderDelete,
}: Props) => {
const queryClient = useQueryClient();
const { mutate: deleteUser, isPending } = useMutation({
mutationFn: (id: string | number) => order_api.delete_order(id),
onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["order_list"] });
toast.success(`Mahsulot o'chirildi`);
setOpenDelete(false);
setOrderDelete(null);
},
onError: (err: AxiosError) => {
const errMessage = err.response?.data as { message: string };
const messageText = errMessage.message;
toast.error(messageText || "Xatolik yuz berdi", {
richColors: true,
position: "top-center",
});
},
});
return (
<Dialog open={opneDelete} onOpenChange={setOpenDelete}>
<DialogContent>
<DialogHeader>
<DialogTitle>Buyurtmani o'chirish</DialogTitle>
<DialogDescription className="text-md font-semibold">
Siz rostan ham {orderDelete?.order_number} sonli buyurtmani
o'chirmoqchimisiz?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
className="bg-blue-600 cursor-pointer hover:bg-blue-600"
onClick={() => setOpenDelete(false)}
>
<X />
Bekor qilish
</Button>
<Button
variant={"destructive"}
onClick={() => orderDelete && deleteUser(orderDelete.id)}
>
{isPending ? (
<Loader2 className="animate-spin" />
) : (
<>
<Trash />
O'chirish
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default OrderDelete;

View File

@@ -0,0 +1,248 @@
import type { Order } from "@/features/order/lib/type";
import formatPhone from "@/shared/lib/formatPhone";
import { Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
import {
Calendar,
CreditCard,
MapIcon,
MessageSquare,
Package,
Phone,
Truck,
User,
X,
} from "lucide-react";
import { type Dispatch, type SetStateAction } from "react";
const deliveryTypeLabel: Record<string, string> = {
YANDEX_GO: "Yandex Go",
DELIVERY_COURIES: "Kuryer orqali yetkazish",
PICKUP: "Ozi olib ketish",
};
interface Props {
detail: boolean;
setDetail: Dispatch<SetStateAction<boolean>>;
order: Order | null;
}
const OrderDetail = ({ detail, setDetail, order }: Props) => {
if (!detail || !order) return null;
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString("uz-UZ", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
const formatPrice = (price: number) => {
return new Intl.NumberFormat("uz-UZ").format(price) + " so'm";
};
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
pending: "bg-yellow-100 text-yellow-800",
confirmed: "bg-blue-100 text-blue-800",
preparing: "bg-purple-100 text-purple-800",
delivering: "bg-indigo-100 text-indigo-800",
completed: "bg-green-100 text-green-800",
cancelled: "bg-red-100 text-red-800",
};
return colors[status] || "bg-gray-100 text-gray-800";
};
return (
<>
<div
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setDetail(false)}
/>
<div className="fixed right-0 top-0 h-full w-full max-w-2xl bg-white shadow-xl z-50 overflow-y-auto">
<div className="sticky top-0 bg-white border-b px-6 py-4 flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">
Buyurtma #{order.order_number}
</h2>
<p className="text-sm text-gray-500">ID: {order.id}</p>
</div>
<button
onClick={() => setDetail(false)}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="p-6 space-y-6">
<div className="bg-gray-50 rounded-lg p-4">
<div className="flex items-center justify-between mb-4">
<span
className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(order.status)}`}
>
{order.status.toUpperCase()}
</span>
<div className="flex items-center text-sm text-gray-500">
<Calendar className="w-4 h-4 mr-1" />
{formatDate(order.created_at)}
</div>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-gray-500">Jami summa</p>
<p className="text-2xl font-bold text-gray-900">
{formatPrice(order.total_price)}
</p>
</div>
<div>
<p className="text-gray-500">Yetkazish narxi</p>
<p className="text-xl font-semibold text-gray-700">
{formatPrice(order.delivery_price)}
</p>
</div>
</div>
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-lg mb-3 flex items-center">
<User className="w-5 h-5 mr-2" />
Mijoz ma'lumotlari
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">Ism:</span>
<span className="font-medium">{order.name}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-500">Telefon:</span>
<a
href={`tel:${order.contact_number}`}
className="font-medium text-blue-600 hover:underline flex items-center"
>
<Phone className="w-4 h-4 mr-1" />
{formatPhone(order.contact_number)}
</a>
</div>
</div>
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-lg mb-3 flex items-center">
<Truck className="w-5 h-5 mr-2" />
Yetkazish ma'lumotlari
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">Turi:</span>
<span className="font-medium capitalize">
{deliveryTypeLabel[order.delivery_type] ??
order.delivery_type}
</span>
</div>
</div>
</div>
<div className="border rounded-lg p-4">
{order.lat && order.long && (
<div className="border rounded-lg p-4 space-y-2">
<h3 className="font-semibold text-lg mb-2 flex items-center">
<MapIcon className="w-5 h-5 mr-2" />
Manzil
</h3>
<div className="w-full h-[300px] rounded-lg overflow-hidden">
<YMaps>
<Map
state={{
center: [order.lat, order.long],
zoom: 16,
}}
width="100%"
height="100%"
>
<Placemark geometry={[order.lat, order.long]} />
</Map>
</YMaps>
</div>
</div>
)}
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-lg mb-3 flex items-center">
<CreditCard className="w-5 h-5 mr-2" />
To'lov turi
</h3>
<p className="text-sm font-medium capitalize">
{order.payment_type === "CASH" ? "Naxt" : "Karta orqali"}
</p>
</div>
{order.comment && (
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-lg mb-3 flex items-center">
<MessageSquare className="w-5 h-5 mr-2" />
Izoh
</h3>
<p className="text-sm text-gray-700">{order.comment}</p>
</div>
)}
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-lg mb-4 flex items-center">
<Package className="w-5 h-5 mr-2" />
Mahsulotlar ({order.items.length})
</h3>
<div className="space-y-3">
{order.items.map((item, index) => (
<div
key={index}
className="flex items-center justify-between py-3 border-b last:border-b-0"
>
<div className="flex-1">
<p className="font-medium">
{item.product?.name_uz || "Noma'lum mahsulot"}
</p>
<p className="text-sm text-gray-500">
{formatPrice(item.product.price)} × {item.quantity}
</p>
</div>
<div className="text-right">
<p className="font-semibold">
{formatPrice(item.product.price * item.quantity)}
</p>
</div>
</div>
))}
</div>
<div className="mt-4 pt-4 border-t space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-500">Mahsulotlar:</span>
<span className="font-medium">
{formatPrice(order.total_price - order.delivery_price)}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500">Yetkazish:</span>
<span className="font-medium">
{formatPrice(order.delivery_price)}
</span>
</div>
<div className="flex justify-between text-lg font-bold pt-2 border-t">
<span>Jami:</span>
<span>{formatPrice(order.total_price)}</span>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default OrderDetail;

View File

@@ -0,0 +1,75 @@
import { order_api } from "@/features/order/lib/api";
import type { Order } from "@/features/order/lib/type";
import OrderDelete from "@/features/order/ui/OrderDelete";
import OrderDetail from "@/features/order/ui/OrderDetail";
import OrderTable from "@/features/order/ui/OrderTable";
import Pagination from "@/shared/ui/pagination";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
const OrderList = () => {
const [currentPage, setCurrentPage] = useState(1);
const limit = 20;
const { data, isLoading, isError, isFetching } = useQuery({
queryKey: ["order_list", currentPage],
queryFn: () => {
return order_api.list({
page: currentPage,
page_size: limit,
});
},
select(data) {
return data.data;
},
});
const [orderDetail, setOrderDetail] = useState<Order | null>(null);
const [detail, setDetail] = useState<boolean>(false);
const [openDelete, setOpenDelete] = useState<boolean>(false);
const [planDelete, setPlanDelete] = useState<Order | null>(null);
const handleDelete = (id: Order) => {
setOpenDelete(true);
setPlanDelete(id);
};
return (
<div className="flex flex-col h-full p-10 w-full">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 gap-4">
<h1 className="text-2xl font-bold">Mahsulotlar</h1>
<OrderDetail
detail={detail}
setDetail={setDetail}
order={orderDetail}
/>
</div>
<OrderTable
orders={data?.results || []}
isLoading={isLoading}
isFetching={isFetching}
isError={isError}
setDetailOpen={setDetail}
handleDelete={handleDelete}
setOrderDetail={setOrderDetail}
/>
<Pagination
currentPage={currentPage}
setCurrentPage={setCurrentPage}
totalPages={data?.total_pages || 1}
/>
<OrderDelete
opneDelete={openDelete}
orderDelete={planDelete}
setOpenDelete={setOpenDelete}
setOrderDelete={setPlanDelete}
/>
</div>
);
};
export default OrderList;

View File

@@ -0,0 +1,205 @@
"use client";
import { order_api } from "@/features/order/lib/api";
import type { Order } from "@/features/order/lib/type";
import formatPhone from "@/shared/lib/formatPhone";
import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/shared/ui/table";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Eye, Loader2, Trash } from "lucide-react";
import type { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";
interface Props {
orders: Order[] | [];
isLoading: boolean;
isFetching: boolean;
isError: boolean;
setDetailOpen: Dispatch<SetStateAction<boolean>>;
handleDelete: (order: Order) => void;
setOrderDetail: Dispatch<SetStateAction<Order | null>>;
}
const deliveryTypeLabel: Record<string, string> = {
YANDEX_GO: "Yandex Go",
DELIVERY_COURIES: "Kuryer orqali yetkazish",
PICKUP: "Ozi olib ketish",
};
type OrderStatus =
| "NEW"
// "PROCESSING" |
| "DONE";
// | "CANCELLED";
const orderStatusLabel: Record<OrderStatus, string> = {
NEW: "Yangi",
// PROCESSING: "Jarayonda",
DONE: "Yakunlangan",
// CANCELLED: "Bekor qilingan",
};
const OrderTable = ({
orders,
isLoading,
isFetching,
isError,
setDetailOpen,
handleDelete,
setOrderDetail,
}: Props) => {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: ({
body,
id,
}: {
body: { status: string };
id: number | string;
}) => order_api.status_update({ body, id }),
onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["order_list"] });
},
onError: () => {
toast.error("Xatolik yuz berdi status o'zgarmadi", {
richColors: true,
position: "top-center",
});
},
});
const handleStatusChange = async (
orderId: number | string,
status: OrderStatus,
) => {
mutate({ id: orderId, body: { status: status } });
};
if (isLoading || isFetching) {
return (
<div className="flex h-full items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
);
}
if (isError) {
return (
<div className="flex h-full items-center justify-center text-red-600">
Maʼlumotlarni yuklashda xatolik yuz berdi
</div>
);
}
return (
<div className="overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>#</TableHead>
<TableHead>Order </TableHead>
<TableHead>Foydalanuvchi</TableHead>
<TableHead>Kontakt</TableHead>
<TableHead>Toʻlov turi</TableHead>
<TableHead>Yetkazib berish</TableHead>
<TableHead>Umumiy narx</TableHead>
<TableHead>Izoh</TableHead>
<TableHead>Holat</TableHead>
<TableHead className="text-right">Harakatlar</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{orders.map((order, index) => (
<TableRow key={order.id}>
<TableCell>{index + 1}</TableCell>
<TableCell>{order.order_number}</TableCell>
<TableCell>
{order.user.first_name} {order.user.last_name} (
{order.user.username})
</TableCell>
<TableCell>{formatPhone(order.contact_number)}</TableCell>
<TableCell>
{order.payment_type === "CASH" ? "Naxt" : "Karta orqali"}
</TableCell>
<TableCell>
{deliveryTypeLabel[order.delivery_type] ?? order.delivery_type}
</TableCell>
<TableCell>{formatPrice(order.total_price, true)}</TableCell>
<TableCell>{order.comment || "-"}</TableCell>
<TableCell>
<Select
value={order.status}
onValueChange={(value) =>
handleStatusChange(order.id, value as OrderStatus)
}
>
<SelectTrigger className="w-[150px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(orderStatusLabel) as OrderStatus[]).map(
(status) => (
<SelectItem key={status} value={status}>
{orderStatusLabel[status]}
</SelectItem>
),
)}
</SelectContent>
</Select>
</TableCell>
<TableCell className="space-x-2 text-right">
<Button
size="sm"
variant="outline"
onClick={() => {
setDetailOpen(true);
setOrderDetail(order);
}}
>
<Eye className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => handleDelete(order)}
>
<Trash className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))}
{orders.length === 0 && (
<TableRow>
<TableCell colSpan={10} className="text-center text-gray-500">
Buyurtmalar topilmadi
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
};
export default OrderTable;

View File

@@ -31,7 +31,7 @@ import { Switch } from "@/shared/ui/switch";
import { Textarea } from "@/shared/ui/textarea";
import { zodResolver } from "@hookform/resolvers/zod";
import { Popover } from "@radix-ui/react-popover";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { Check, ChevronsUpDown, Loader2, Upload, XIcon } from "lucide-react";
import { useState } from "react";
@@ -45,6 +45,7 @@ interface Props {
}
const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
const queryClient = useQueryClient();
const form = useForm<z.infer<typeof createPlanFormData>>({
resolver: zodResolver(createPlanFormData),
defaultValues: {
@@ -79,6 +80,7 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
richColors: true,
position: "top-center",
});
queryClient.refetchQueries({ queryKey: ["product_list"] });
setDialogOpen(false);
},
onError: (err: AxiosError) => {
@@ -92,10 +94,11 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
mutationFn: ({ body, id }: { id: string; body: FormData }) =>
plans_api.update({ body, id }),
onSuccess() {
toast.success("Mahsulot qo'shildi", {
toast.success("Mahsulot tahrirlandi", {
richColors: true,
position: "top-center",
});
queryClient.refetchQueries({ queryKey: ["product_list"] });
setDialogOpen(false);
},
onError: (err: AxiosError) => {
@@ -425,9 +428,15 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
<FormLabel>Mavjud miqdor</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
type="text"
placeholder="Mahsulotning qolgan miqdori"
className="h-12"
value={field.value ? formatPrice(field.value) : ""}
onChange={(e) => {
// faqat raqamlarni qoldiramiz
const rawValue = e.target.value.replace(/\D/g, "");
field.onChange(rawValue ? Number(rawValue) : 0);
}}
/>
</FormControl>
<FormMessage />
@@ -443,9 +452,15 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
<FormLabel>Minimal miqdor</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
type="text"
placeholder="Eng kamida nechta zakaz qilishi"
className="h-12"
value={field.value ? formatPrice(field.value) : ""}
onChange={(e) => {
// faqat raqamlarni qoldiramiz
const rawValue = e.target.value.replace(/\D/g, "");
field.onChange(rawValue ? Number(rawValue) : 0);
}}
/>
</FormControl>
<FormMessage />
@@ -608,20 +623,19 @@ const AddedPlan = ({ initialValues, setDialogOpen }: Props) => {
? "Yangi rasm tanlash"
: "Rasm tanlang"}
</p>
{field.value && field.value instanceof File && (
<div className="mt-3 text-center">
<p className="text-sm font-medium text-gray-700">
Yangi tanlangan: {field.value.name}
</p>
<img
src={URL.createObjectURL(field.value)}
alt="preview"
className="mt-2 mx-auto h-20 w-20 object-cover rounded-lg"
/>
</div>
)}
</FormLabel>
{field.value && field.value instanceof File && (
<div className="mt-3 text-center">
<p className="text-sm font-medium text-gray-700">
Yangi tanlangan: {field.value.name}
</p>
<img
src={URL.createObjectURL(field.value)}
alt="preview"
className="mt-2 mx-auto h-20 w-20 object-cover rounded-lg"
/>
</div>
)}
</div>
</FormControl>
<FormMessage />

View File

@@ -20,10 +20,12 @@ import {
TableHeader,
TableRow,
} from "@/shared/ui/table";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import clsx from "clsx";
import { Edit, Eye, Loader2, Trash } from "lucide-react";
import type { Dispatch, SetStateAction } from "react";
import { toast } from "sonner";
interface Props {
products: Product[] | [];
@@ -66,6 +68,36 @@ const ProductTable = ({
},
});
const queryClient = useQueryClient();
const { mutate: updated } = useMutation({
mutationFn: ({ body, id }: { id: string; body: FormData }) =>
plans_api.update({ body, id }),
onSuccess() {
toast.success("Mahsulot statusi o'zgardi", {
richColors: true,
position: "top-center",
});
queryClient.refetchQueries({ queryKey: ["product_list"] });
setDialogOpen(false);
},
onError: (err: AxiosError) => {
toast.error((err.response?.data as string) || "Xatolik yuz berdi", {
richColors: true,
position: "top-center",
});
},
});
const handleStatusChange = async (
product: string,
status: "true" | "false",
) => {
const formData = new FormData();
formData.append("is_active", status);
updated({ body: formData, id: product });
};
if (isLoading || isFetching) {
return (
<div className="flex h-full items-center justify-center">
@@ -130,7 +162,12 @@ const ProductTable = ({
product.is_active ? "text-green-600" : "text-red-600",
)}
>
<Select value={product.is_active ? "true" : "false"}>
<Select
value={product.is_active ? "true" : "false"}
onValueChange={(value) =>
handleStatusChange(product.id, value as "true" | "false")
}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Holati" />
</SelectTrigger>

View File

@@ -1,5 +1,12 @@
import OrderList from "@/features/order/ui/OrderList";
import SidebarLayout from "@/SidebarLayout";
const Orders = () => {
return <>{/* <OrdersList /> */}</>;
return (
<SidebarLayout>
<OrderList />
</SidebarLayout>
);
};
export default Orders;

View File

@@ -29,6 +29,6 @@ export const API_URLS = {
OrderStatus: (id: string | number) =>
`${API_V}admin/order/${id}/status_update/`,
CreateProduct: `${API_V}admin/product/create/`,
UpdateProduct: (id: string) => `${API_V}admin/user/${id}/update/`,
UpdateProduct: (id: string) => `${API_V}admin/product/${id}/update/`,
DeleteProdut: (id: string) => `${API_V}admin/product/${id}/delete/`,
};