user update price type
This commit is contained in:
@@ -43,4 +43,15 @@ export const user_api = {
|
|||||||
const res = await httpClient.post(API_URLS.PasswordSet(id), body);
|
const res = await httpClient.post(API_URLS.PasswordSet(id), body);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async price_type_set({
|
||||||
|
body,
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
id: string | number;
|
||||||
|
body: { ids: number[] };
|
||||||
|
}) {
|
||||||
|
const res = await httpClient.post(API_URLS.PriceTypeSet(id), body);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface UserData {
|
|||||||
short_name: string;
|
short_name: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
password_set: boolean;
|
password_set: boolean;
|
||||||
|
price_types: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserListRes {
|
export interface UserListRes {
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import price_type_api from "@/features/price-type/lib/api";
|
||||||
import { user_api } from "@/features/users/lib/api";
|
import { user_api } from "@/features/users/lib/api";
|
||||||
import type { UserData } from "@/features/users/lib/data";
|
import type { UserData } from "@/features/users/lib/data";
|
||||||
import formatDate from "@/shared/lib/formatDate";
|
import formatDate from "@/shared/lib/formatDate";
|
||||||
|
import { cn } from "@/shared/lib/utils";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/shared/ui/command";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -19,6 +28,7 @@ import {
|
|||||||
} from "@/shared/ui/form";
|
} from "@/shared/ui/form";
|
||||||
import { Input } from "@/shared/ui/input";
|
import { Input } from "@/shared/ui/input";
|
||||||
import { Label } from "@/shared/ui/label";
|
import { Label } from "@/shared/ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -28,10 +38,10 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/shared/ui/table";
|
} from "@/shared/ui/table";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import { Edit } from "lucide-react";
|
import { Check, ChevronsUpDown, Edit } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
@@ -49,7 +59,28 @@ interface UserListProps {
|
|||||||
export function UserCard({ users }: UserListProps) {
|
export function UserCard({ users }: UserListProps) {
|
||||||
const [edit, setEdit] = useState<number | string | null>(null);
|
const [edit, setEdit] = useState<number | string | null>(null);
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [openPriceId, setOpenPriceId] = useState<number | null>(null);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const [values, setValues] = useState<Record<string | number, string[]>>({});
|
||||||
|
|
||||||
|
const { data } = useQuery({
|
||||||
|
queryKey: ["price_type"],
|
||||||
|
queryFn: () => price_type_api.list(),
|
||||||
|
select(data) {
|
||||||
|
return data.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialValues: Record<string | number, string[]> = {};
|
||||||
|
users.forEach((user) => {
|
||||||
|
if (user.price_types) {
|
||||||
|
initialValues[user.id] = user.price_types.map((id) => id.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setValues(initialValues);
|
||||||
|
}, [users]);
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof passwordSet>>({
|
const form = useForm<z.infer<typeof passwordSet>>({
|
||||||
resolver: zodResolver(passwordSet),
|
resolver: zodResolver(passwordSet),
|
||||||
@@ -87,6 +118,52 @@ export function UserCard({ users }: UserListProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mutate: price_type_set } = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
id,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
id: number | string;
|
||||||
|
body: { ids: number[] };
|
||||||
|
}) => user_api.price_type_set({ body, id }),
|
||||||
|
onSuccess: () => {
|
||||||
|
setOpenPriceId(null);
|
||||||
|
queryClient.refetchQueries({ queryKey: ["user_list"] });
|
||||||
|
toast.success("Narx qo'yildi", {
|
||||||
|
richColors: true,
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (err: AxiosError) => {
|
||||||
|
const errData = (err.response?.data as { data: string }).data;
|
||||||
|
const errMessage = (err.response?.data as { message: string }).message;
|
||||||
|
|
||||||
|
toast.error(errData || errMessage || "Xatolik yuz berdi", {
|
||||||
|
richColors: true,
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleValue = (userId: string | number, value: string) => {
|
||||||
|
let newUserValues: string[] = [];
|
||||||
|
|
||||||
|
setValues((prev) => {
|
||||||
|
const userValues = prev[userId] || [];
|
||||||
|
newUserValues = userValues.includes(value)
|
||||||
|
? userValues.filter((v) => v !== value)
|
||||||
|
: [...userValues, value];
|
||||||
|
return { ...prev, [userId]: newUserValues };
|
||||||
|
});
|
||||||
|
const currentUserValues = values[userId] || [];
|
||||||
|
const nextUserValues = currentUserValues.includes(value)
|
||||||
|
? currentUserValues.filter((v) => v !== value)
|
||||||
|
: [...currentUserValues, value];
|
||||||
|
|
||||||
|
setValues((prev) => ({ ...prev, [userId]: nextUserValues }));
|
||||||
|
price_type_set({ body: { ids: nextUserValues.map(Number) }, id: userId });
|
||||||
|
};
|
||||||
|
|
||||||
function onSubmit(value: z.infer<typeof passwordSet>) {
|
function onSubmit(value: z.infer<typeof passwordSet>) {
|
||||||
if (edit) {
|
if (edit) {
|
||||||
mutate({ body: { password: value.password }, id: edit });
|
mutate({ body: { password: value.password }, id: edit });
|
||||||
@@ -102,107 +179,178 @@ export function UserCard({ users }: UserListProps) {
|
|||||||
<TableHead>To'liq ism</TableHead>
|
<TableHead>To'liq ism</TableHead>
|
||||||
<TableHead>Username</TableHead>
|
<TableHead>Username</TableHead>
|
||||||
<TableHead>Ro'yxatdan o'tgan</TableHead>
|
<TableHead>Ro'yxatdan o'tgan</TableHead>
|
||||||
|
<TableHead>Biriktirilgan narx</TableHead>
|
||||||
<TableHead className="text-right">Amallar</TableHead>
|
<TableHead className="text-right">Amallar</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user) => (
|
{users.map((user) => {
|
||||||
<TableRow key={user.id} className="hover:bg-muted/50">
|
const userValues = values[user.id] || [];
|
||||||
<TableCell className="font-medium">{user.id}</TableCell>
|
return (
|
||||||
<TableCell>
|
<TableRow key={user.id} className="hover:bg-muted/50">
|
||||||
<div className="flex flex-col">
|
<TableCell className="font-medium">{user.id}</TableCell>
|
||||||
<span className="font-medium">{user.name}</span>
|
<TableCell>
|
||||||
</div>
|
<div className="flex flex-col">
|
||||||
</TableCell>
|
<span className="font-medium">{user.name}</span>
|
||||||
<TableCell>
|
</div>
|
||||||
<span className="text-muted-foreground">@{user.username}</span>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
@{user.username}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{formatDate.format(user.created_at, "DD-MM-YYYY")}
|
{formatDate.format(user.created_at, "DD-MM-YYYY")}
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell>
|
||||||
<Dialog
|
<Popover
|
||||||
open={open && edit === user.id}
|
open={openPriceId === user.id}
|
||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) =>
|
||||||
setOpen(isOpen);
|
setOpenPriceId(isOpen ? user.id : null)
|
||||||
if (!isOpen) {
|
|
||||||
setEdit(null);
|
|
||||||
form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
>
|
||||||
>
|
<PopoverTrigger asChild>
|
||||||
<DialogTrigger asChild>
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
size="sm"
|
role="combobox"
|
||||||
variant="outline"
|
aria-expanded={openPriceId === user.id}
|
||||||
className="cursor-pointer"
|
className="w-[200px] justify-between cursor-pointer"
|
||||||
onClick={() => {
|
>
|
||||||
setOpen(true);
|
<span className="truncate">
|
||||||
setEdit(user.id);
|
{userValues.length > 0
|
||||||
}}
|
? userValues
|
||||||
>
|
.map(
|
||||||
<Edit className="h-4 w-4 mr-2" />
|
(id) =>
|
||||||
Parol {user.password_set ? "O'zgartirish" : "Qo'yish"}
|
data?.find(
|
||||||
</Button>
|
(item) => item.id.toString() === id,
|
||||||
</DialogTrigger>
|
)?.name,
|
||||||
<DialogContent>
|
)
|
||||||
<DialogHeader className="text-xl font-semibold">
|
.filter(Boolean)
|
||||||
Parolni {user.password_set ? "o'zgartirish" : "qo'yish"}
|
.join(", ")
|
||||||
</DialogHeader>
|
: "Narxni tanlang"}
|
||||||
<div>
|
</span>
|
||||||
<Form {...form}>
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
<form
|
</Button>
|
||||||
className="space-y-4"
|
</PopoverTrigger>
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
>
|
<PopoverContent className="w-[200px] p-0" align="start">
|
||||||
<FormField
|
<Command>
|
||||||
name="password"
|
<CommandList>
|
||||||
control={form.control}
|
<CommandEmpty>Topilmadi.</CommandEmpty>
|
||||||
render={({ field }) => (
|
<CommandGroup>
|
||||||
<FormItem>
|
{data?.map((option) => {
|
||||||
<Label>Yangi parol</Label>
|
const isSelected = userValues.includes(
|
||||||
<FormControl>
|
option.id.toString(),
|
||||||
<Input
|
);
|
||||||
type="password"
|
|
||||||
placeholder="12345678"
|
return (
|
||||||
className="h-12 focus-visible:ring-0"
|
<CommandItem
|
||||||
{...field}
|
key={option.id}
|
||||||
|
onSelect={() =>
|
||||||
|
toggleValue(user.id, option.id.toString())
|
||||||
|
}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4",
|
||||||
|
isSelected ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
{option.name}
|
||||||
<FormMessage />
|
</CommandItem>
|
||||||
</FormItem>
|
);
|
||||||
)}
|
})}
|
||||||
/>
|
</CommandGroup>
|
||||||
<div className="flex justify-end gap-2">
|
</CommandList>
|
||||||
<Button
|
</Command>
|
||||||
type="button"
|
</PopoverContent>
|
||||||
variant="outline"
|
</Popover>
|
||||||
onClick={() => {
|
</TableCell>
|
||||||
setOpen(false);
|
<TableCell className="text-right">
|
||||||
setEdit(null);
|
<Dialog
|
||||||
form.reset();
|
open={open && edit === user.id}
|
||||||
}}
|
onOpenChange={(isOpen) => {
|
||||||
>
|
setOpen(isOpen);
|
||||||
Bekor qilish
|
if (!isOpen) {
|
||||||
</Button>
|
setEdit(null);
|
||||||
<Button
|
form.reset();
|
||||||
type="submit"
|
}
|
||||||
className="bg-blue-500 hover:bg-blue-600 cursor-pointer"
|
}}
|
||||||
>
|
>
|
||||||
Tasdiqlash
|
<DialogTrigger asChild>
|
||||||
</Button>
|
<Button
|
||||||
</div>
|
size="sm"
|
||||||
</form>
|
variant="outline"
|
||||||
</Form>
|
className="cursor-pointer"
|
||||||
</div>
|
onClick={() => {
|
||||||
</DialogContent>
|
setOpen(true);
|
||||||
</Dialog>
|
setEdit(user.id);
|
||||||
</TableCell>
|
}}
|
||||||
</TableRow>
|
>
|
||||||
))}
|
<Edit className="h-4 w-4 mr-2" />
|
||||||
|
Parol {user.password_set ? "O'zgartirish" : "Qo'yish"}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader className="text-xl font-semibold">
|
||||||
|
Parolni {user.password_set ? "o'zgartirish" : "qo'yish"}
|
||||||
|
</DialogHeader>
|
||||||
|
<div>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="space-y-4"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
name="password"
|
||||||
|
control={form.control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<Label>Yangi parol</Label>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="12345678"
|
||||||
|
className="h-12 focus-visible:ring-0"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setEdit(null);
|
||||||
|
form.reset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bekor qilish
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="bg-blue-500 hover:bg-blue-600 cursor-pointer"
|
||||||
|
>
|
||||||
|
Tasdiqlash
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,4 +47,6 @@ export const API_URLS = {
|
|||||||
Update_Pyment_Type: (id: number) =>
|
Update_Pyment_Type: (id: number) =>
|
||||||
`${API_V}admin/product/${id}/update_payment_type/`,
|
`${API_V}admin/product/${id}/update_payment_type/`,
|
||||||
Import_Balance: `${API_V}admin/product/import/balance/`,
|
Import_Balance: `${API_V}admin/product/import/balance/`,
|
||||||
|
PriceTypeSet: (id: number | string) =>
|
||||||
|
`${API_V}admin/user/${id}/set_price_type/`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user