order page and user page update
This commit is contained in:
@@ -38,6 +38,8 @@ export interface Order {
|
|||||||
items: Item[];
|
items: Item[];
|
||||||
created_at: string;
|
created_at: string;
|
||||||
long: number;
|
long: number;
|
||||||
|
delivery_date: string;
|
||||||
|
delivery_time: string;
|
||||||
lat: number;
|
lat: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import formatPrice from "@/shared/lib/formatPrice";
|
|||||||
import { Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
|
import { Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
|
Clock3,
|
||||||
MapIcon,
|
MapIcon,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Package,
|
Package,
|
||||||
@@ -126,6 +127,26 @@ const OrderDetail = ({ detail, setDetail, order }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{order.delivery_date && (
|
||||||
|
<div className="border rounded-lg p-4">
|
||||||
|
<h3 className="font-semibold text-lg mb-3 flex items-center">
|
||||||
|
<Calendar className="w-5 h-5 mr-2" />
|
||||||
|
Yetkazish sanasi
|
||||||
|
</h3>
|
||||||
|
<p className="text-lg text-gray-700">{order.delivery_date}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{order.delivery_time && (
|
||||||
|
<div className="border rounded-lg p-4">
|
||||||
|
<h3 className="font-semibold text-lg mb-3 flex items-center">
|
||||||
|
<Clock3 className="w-5 h-5 mr-2" />
|
||||||
|
Yetkazish vaqti
|
||||||
|
</h3>
|
||||||
|
<p className="text-lg text-gray-700">{order.delivery_time}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="border rounded-lg p-4">
|
<div className="border rounded-lg p-4">
|
||||||
<h3 className="font-semibold text-lg mb-4 flex items-center">
|
<h3 className="font-semibold text-lg mb-4 flex items-center">
|
||||||
<Package className="w-5 h-5 mr-2" />
|
<Package className="w-5 h-5 mr-2" />
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ const OrderTable = ({
|
|||||||
<TableHead>Foydalanuvchi</TableHead>
|
<TableHead>Foydalanuvchi</TableHead>
|
||||||
<TableHead>Umumiy narx</TableHead>
|
<TableHead>Umumiy narx</TableHead>
|
||||||
<TableHead>Izoh</TableHead>
|
<TableHead>Izoh</TableHead>
|
||||||
|
<TableHead>Yetkazish sanasi</TableHead>
|
||||||
|
<TableHead>Yetkazish soati</TableHead>
|
||||||
<TableHead>Holat</TableHead>
|
<TableHead>Holat</TableHead>
|
||||||
<TableHead className="text-right">Harakatlar</TableHead>
|
<TableHead className="text-right">Harakatlar</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -121,6 +123,8 @@ const OrderTable = ({
|
|||||||
<TableCell>{order.user.username}</TableCell>
|
<TableCell>{order.user.username}</TableCell>
|
||||||
<TableCell>{formatPrice(order.total_price, true)}</TableCell>
|
<TableCell>{formatPrice(order.total_price, true)}</TableCell>
|
||||||
<TableCell>{order.comment || "-"}</TableCell>
|
<TableCell>{order.comment || "-"}</TableCell>
|
||||||
|
<TableCell>{order?.delivery_date || "-"}</TableCell>
|
||||||
|
<TableCell>{order?.delivery_time || "-"}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Select
|
<Select
|
||||||
value={order.status}
|
value={order.status}
|
||||||
|
|||||||
@@ -26,4 +26,9 @@ export const user_api = {
|
|||||||
const res = await httpClient.delete(`${API_URLS.UserDelete(id)}`);
|
const res = await httpClient.delete(`${API_URLS.UserDelete(id)}`);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async import_user() {
|
||||||
|
const res = await httpClient.post(API_URLS.Import_User);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
export interface UserData {
|
export interface UserData {
|
||||||
id: string;
|
id: 3;
|
||||||
username: string;
|
username: string;
|
||||||
last_login: null | string;
|
first_name: string;
|
||||||
date_joined: string;
|
last_name: string;
|
||||||
is_superuser: boolean;
|
middle_name: null | string;
|
||||||
role: "admin" | "user" | "moderator";
|
gender: "M" | "F" | null;
|
||||||
|
region: null | string;
|
||||||
|
address: null | string;
|
||||||
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserListRes {
|
export interface UserListRes {
|
||||||
@@ -18,7 +21,13 @@ export interface UserListRes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserCreateReq {
|
export interface UserCreateReq {
|
||||||
|
id: 3;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
first_name: string;
|
||||||
is_superuser: boolean;
|
last_name: string;
|
||||||
|
middle_name: null | string;
|
||||||
|
gender: "M" | "F" | null;
|
||||||
|
region: null | string;
|
||||||
|
address: null | string;
|
||||||
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,88 +3,120 @@
|
|||||||
import type { UserData } from "@/features/users/lib/data";
|
import type { UserData } from "@/features/users/lib/data";
|
||||||
import { Avatar, AvatarFallback } from "@/shared/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/shared/ui/avatar";
|
||||||
import { Badge } from "@/shared/ui/badge";
|
import { Badge } from "@/shared/ui/badge";
|
||||||
import { Button } from "@/shared/ui/button";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/shared/ui/card";
|
import { Card, CardContent, CardHeader } from "@/shared/ui/card";
|
||||||
import { Clock, Edit, Trash2 } from "lucide-react";
|
import { Calendar, MapPin } from "lucide-react";
|
||||||
import { type Dispatch, type SetStateAction } from "react";
|
import { type Dispatch, type SetStateAction } from "react";
|
||||||
|
|
||||||
export function UserCard({
|
export function UserCard({
|
||||||
user,
|
user,
|
||||||
setEditingUser,
|
// setEditingUser,
|
||||||
setDialogOpen,
|
// setDialogOpen,
|
||||||
setOpenDelete,
|
// setOpenDelete,
|
||||||
setUserDelete,
|
// setUserDelete,
|
||||||
}: {
|
}: {
|
||||||
user: UserData;
|
user: UserData;
|
||||||
setEditingUser: (user: UserData) => void;
|
setEditingUser?: (user: UserData) => void;
|
||||||
setDialogOpen: (open: boolean) => void;
|
setDialogOpen?: (open: boolean) => void;
|
||||||
setOpenDelete: Dispatch<SetStateAction<boolean>>;
|
setOpenDelete?: Dispatch<SetStateAction<boolean>>;
|
||||||
setUserDelete: Dispatch<SetStateAction<UserData | null>>;
|
setUserDelete?: Dispatch<SetStateAction<UserData | null>>;
|
||||||
}) {
|
}) {
|
||||||
const getRoleColor = (role: UserData["role"]) => {
|
const getGenderColor = (gender: "M" | "F" | null) => {
|
||||||
switch (role) {
|
switch (gender) {
|
||||||
case "admin":
|
case "M":
|
||||||
return "bg-red-100 text-red-800";
|
|
||||||
case "moderator":
|
|
||||||
return "bg-blue-100 text-blue-800";
|
return "bg-blue-100 text-blue-800";
|
||||||
case "user":
|
case "F":
|
||||||
return "bg-green-100 text-green-800";
|
return "bg-pink-100 text-pink-800";
|
||||||
default:
|
default:
|
||||||
return "bg-gray-100 text-gray-800";
|
return "bg-gray-100 text-gray-800";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGenderLabel = (gender: "M" | "F" | null) => {
|
||||||
|
switch (gender) {
|
||||||
|
case "M":
|
||||||
|
return "Erkak";
|
||||||
|
case "F":
|
||||||
|
return "Ayol";
|
||||||
|
default:
|
||||||
|
return "Ko'rsatilmagan";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitials = () => {
|
||||||
|
if (user.first_name && user.last_name) {
|
||||||
|
return `${user.first_name[0]}${user.last_name[0]}`.toUpperCase();
|
||||||
|
}
|
||||||
|
return user.username.substring(0, 2).toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFullName = () => {
|
||||||
|
const parts = [user.first_name, user.middle_name, user.last_name].filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
|
return parts.length > 0 ? parts.join(" ") : user.username;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Card className="group hover:shadow-md transition-shadow">
|
<Card className="group hover:shadow-md transition-shadow">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarFallback className="bg-green-100 text-green-700">
|
<AvatarFallback className="bg-gradient-to-br from-purple-100 to-blue-100 text-purple-700">
|
||||||
{user.username
|
{getInitials()}
|
||||||
.split(" ")
|
|
||||||
.map((n) => n[0])
|
|
||||||
.join("")
|
|
||||||
.toUpperCase()}
|
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold">{user.username}</h3>
|
<h3 className="font-semibold">{getFullName()}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">@{user.username}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge className={getRoleColor(user.role)}>
|
<Badge className={getGenderColor(user.gender)}>
|
||||||
{user.role === "admin"
|
{getGenderLabel(user.gender)}
|
||||||
? "Admin"
|
|
||||||
: user.role === "user"
|
|
||||||
? "Foydalanuvchi"
|
|
||||||
: user.role}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-3">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Clock className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
<span>
|
<span>
|
||||||
{"Kirgan vaqt"} {new Date(user.date_joined).toLocaleDateString()}
|
Ro'yxatdan o'tgan:{" "}
|
||||||
|
{new Date(user.created_at).toLocaleDateString("uz-UZ", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user.last_login && (
|
{user.region && (
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
{"Ohirgi marta kirgan"}:{" "}
|
<MapPin className="h-4 w-4" />
|
||||||
{new Date(user.last_login).toLocaleDateString()}
|
<span>Hudud: {user.region}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
{user.address && (
|
||||||
|
<div className="flex items-start gap-2 text-sm text-muted-foreground">
|
||||||
|
<MapPin className="h-4 w-4 mt-0.5" />
|
||||||
|
<span>Manzil: {user.address}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ID ma'lumoti */}
|
||||||
|
<div className="pt-2 border-t text-xs text-muted-foreground">
|
||||||
|
ID: {user.id}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Uncommnet qilish uchun tugmalar
|
||||||
|
<div className="flex items-center gap-2 pt-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditingUser(user);
|
setEditingUser(user);
|
||||||
setDialogOpen(true);
|
setDialogOpen?.(true);
|
||||||
}}
|
}}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
@@ -96,17 +128,15 @@ export function UserCard({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenDelete(true);
|
setOpenDelete(true);
|
||||||
if (setUserDelete) {
|
|
||||||
setUserDelete(user);
|
setUserDelete(user);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
className="text-red-600 hover:text-red-700"
|
className="text-red-600 hover:text-red-700"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,16 @@ interface Props {
|
|||||||
data: UserData[] | undefined;
|
data: UserData[] | undefined;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
setDialogOpen: Dispatch<SetStateAction<boolean>>;
|
setDialogOpen?: Dispatch<SetStateAction<boolean>>;
|
||||||
setEditingUser: Dispatch<SetStateAction<UserData | null>>;
|
setEditingUser?: Dispatch<SetStateAction<UserData | null>>;
|
||||||
setOpenDelete: Dispatch<SetStateAction<boolean>>;
|
setOpenDelete?: Dispatch<SetStateAction<boolean>>;
|
||||||
setUserDelete: Dispatch<SetStateAction<UserData | null>>;
|
setUserDelete?: Dispatch<SetStateAction<UserData | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserTable = ({
|
const UserTable = ({
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
setDialogOpen,
|
|
||||||
setEditingUser,
|
setEditingUser,
|
||||||
setOpenDelete,
|
setOpenDelete,
|
||||||
setUserDelete,
|
setUserDelete,
|
||||||
@@ -46,9 +45,9 @@ const UserTable = ({
|
|||||||
setOpenDelete={setOpenDelete}
|
setOpenDelete={setOpenDelete}
|
||||||
setUserDelete={setUserDelete}
|
setUserDelete={setUserDelete}
|
||||||
key={user.id}
|
key={user.id}
|
||||||
setDialogOpen={setDialogOpen}
|
// setDialogOpen={setDialogOpen}
|
||||||
setEditingUser={setEditingUser}
|
setEditingUser={setEditingUser}
|
||||||
user={{ ...user, role: user.is_superuser ? "admin" : "user" }}
|
user={{ ...user }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import { user_api } from "@/features/users/lib/api";
|
import { user_api } from "@/features/users/lib/api";
|
||||||
import type { UserData, UserListRes } from "@/features/users/lib/data";
|
import type { UserData } from "@/features/users/lib/data";
|
||||||
import DeleteUser from "@/features/users/ui/DeleteUser";
|
import DeleteUser from "@/features/users/ui/DeleteUser";
|
||||||
import Filter from "@/features/users/ui/Filter";
|
|
||||||
import UserTable from "@/features/users/ui/UserTable";
|
import UserTable from "@/features/users/ui/UserTable";
|
||||||
|
import { Button } from "@/shared/ui/button";
|
||||||
import Pagination from "@/shared/ui/pagination";
|
import Pagination from "@/shared/ui/pagination";
|
||||||
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const UsersList = () => {
|
const UsersList = () => {
|
||||||
const [editingUser, setEditingUser] = useState<UserData | null>(null);
|
// const [editingUser, setEditingUser] = useState<UserData | null>(null);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const [opneDelete, setOpenDelete] = useState(false);
|
const [opneDelete, setOpenDelete] = useState(false);
|
||||||
const [userDelete, setUserDelete] = useState<UserData | null>(null);
|
const [userDelete, setUserDelete] = useState<UserData | null>(null);
|
||||||
|
|
||||||
const [regionValue, setRegionValue] = useState<UserListRes | null>(null);
|
// const [regionValue, setRegionValue] = useState<UserListRes | null>(null);
|
||||||
const limit = 20;
|
const limit = 20;
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const { data, isLoading, isError } = useQuery({
|
const { data, isLoading, isError } = useQuery({
|
||||||
queryKey: ["user_list", currentPage, regionValue],
|
queryKey: ["user_list", currentPage],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return user_api.list({
|
return user_api.list({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
@@ -30,28 +33,55 @@ const UsersList = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const { mutate, isPending } = useMutation({
|
||||||
|
mutationFn: () => user_api.import_user(),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Foydalanuvchilar import qilindi", {
|
||||||
|
richColors: true,
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
queryClient.refetchQueries({ queryKey: ["user_list"] });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Xatolik yuz berdi", {
|
||||||
|
richColors: true,
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full p-10 w-full">
|
<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">
|
<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">Foydalanuvchilar ro'yxati</h1>
|
<h1 className="text-2xl font-bold">Foydalanuvchilar ro'yxati</h1>
|
||||||
|
<Button
|
||||||
|
className="bg-blue-500 h-12 text-md hover:bg-blue-500 cursor-pointer"
|
||||||
|
onClick={() => mutate()}
|
||||||
|
>
|
||||||
|
{isPending ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Foydalanuvchini import qilish"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Filter
|
{/* <Filter
|
||||||
dialogOpen={dialogOpen}
|
dialogOpen={dialogOpen}
|
||||||
setDialogOpen={setDialogOpen}
|
setDialogOpen={setDialogOpen}
|
||||||
editingUser={editingUser}
|
editingUser={editingUser}
|
||||||
setEditingUser={setEditingUser}
|
setEditingUser={setEditingUser}
|
||||||
setRegionValue={setRegionValue}
|
setRegionValue={setRegionValue}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UserTable
|
<UserTable
|
||||||
data={data?.results}
|
data={data?.results}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setEditingUser={setEditingUser}
|
// setEditingUser={setEditingUser}
|
||||||
isError={isError}
|
isError={isError}
|
||||||
setDialogOpen={setDialogOpen}
|
// setDialogOpen={setDialogOpen}
|
||||||
setOpenDelete={setOpenDelete}
|
setOpenDelete={setOpenDelete}
|
||||||
setUserDelete={setUserDelete}
|
setUserDelete={setUserDelete}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -32,4 +32,5 @@ export const API_URLS = {
|
|||||||
UpdateProduct: (id: string) => `${API_V}admin/product/${id}/update/`,
|
UpdateProduct: (id: string) => `${API_V}admin/product/${id}/update/`,
|
||||||
DeleteProdut: (id: string) => `${API_V}admin/product/${id}/delete/`,
|
DeleteProdut: (id: string) => `${API_V}admin/product/${id}/delete/`,
|
||||||
QuestionnaireList: `${API_V}admin/questionnaire/list/`,
|
QuestionnaireList: `${API_V}admin/questionnaire/list/`,
|
||||||
|
Import_User: `${API_V}admin/user/import_users/`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user