user update price type

This commit is contained in:
Samandar Turgunboyev
2026-02-17 19:38:30 +05:00
parent c006dfd907
commit 33eed28b83
4 changed files with 259 additions and 97 deletions

View File

@@ -43,4 +43,15 @@ export const user_api = {
const res = await httpClient.post(API_URLS.PasswordSet(id), body);
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;
},
};

View File

@@ -6,6 +6,7 @@ export interface UserData {
short_name: string;
created_at: string;
password_set: boolean;
price_types: number[];
}
export interface UserListRes {

View File

@@ -1,9 +1,18 @@
"use client";
import price_type_api from "@/features/price-type/lib/api";
import { user_api } from "@/features/users/lib/api";
import type { UserData } from "@/features/users/lib/data";
import formatDate from "@/shared/lib/formatDate";
import { cn } from "@/shared/lib/utils";
import { Button } from "@/shared/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandItem,
CommandList,
} from "@/shared/ui/command";
import {
Dialog,
DialogContent,
@@ -19,6 +28,7 @@ import {
} from "@/shared/ui/form";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
import {
Table,
TableBody,
@@ -28,10 +38,10 @@ import {
TableRow,
} from "@/shared/ui/table";
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 { Edit } from "lucide-react";
import { useState } from "react";
import { Check, ChevronsUpDown, Edit } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
@@ -49,7 +59,28 @@ interface UserListProps {
export function UserCard({ users }: UserListProps) {
const [edit, setEdit] = useState<number | string | null>(null);
const [open, setOpen] = useState<boolean>(false);
const [openPriceId, setOpenPriceId] = useState<number | null>(null);
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>>({
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>) {
if (edit) {
mutate({ body: { password: value.password }, id: edit });
@@ -102,107 +179,178 @@ export function UserCard({ users }: UserListProps) {
<TableHead>To'liq ism</TableHead>
<TableHead>Username</TableHead>
<TableHead>Ro'yxatdan o'tgan</TableHead>
<TableHead>Biriktirilgan narx</TableHead>
<TableHead className="text-right">Amallar</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id} className="hover:bg-muted/50">
<TableCell className="font-medium">{user.id}</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">{user.name}</span>
</div>
</TableCell>
<TableCell>
<span className="text-muted-foreground">@{user.username}</span>
</TableCell>
{users.map((user) => {
const userValues = values[user.id] || [];
return (
<TableRow key={user.id} className="hover:bg-muted/50">
<TableCell className="font-medium">{user.id}</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">{user.name}</span>
</div>
</TableCell>
<TableCell>
<span className="text-muted-foreground">
@{user.username}
</span>
</TableCell>
<TableCell>
<span className="text-sm text-muted-foreground">
{formatDate.format(user.created_at, "DD-MM-YYYY")}
</span>
</TableCell>
<TableCell className="text-right">
<Dialog
open={open && edit === user.id}
onOpenChange={(isOpen) => {
setOpen(isOpen);
if (!isOpen) {
setEdit(null);
form.reset();
<TableCell>
<span className="text-sm text-muted-foreground">
{formatDate.format(user.created_at, "DD-MM-YYYY")}
</span>
</TableCell>
<TableCell>
<Popover
open={openPriceId === user.id}
onOpenChange={(isOpen) =>
setOpenPriceId(isOpen ? user.id : null)
}
}}
>
<DialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="cursor-pointer"
onClick={() => {
setOpen(true);
setEdit(user.id);
}}
>
<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}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openPriceId === user.id}
className="w-[200px] justify-between cursor-pointer"
>
<span className="truncate">
{userValues.length > 0
? userValues
.map(
(id) =>
data?.find(
(item) => item.id.toString() === id,
)?.name,
)
.filter(Boolean)
.join(", ")
: "Narxni tanlang"}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<CommandList>
<CommandEmpty>Topilmadi.</CommandEmpty>
<CommandGroup>
{data?.map((option) => {
const isSelected = userValues.includes(
option.id.toString(),
);
return (
<CommandItem
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>
<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>
))}
{option.name}
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</TableCell>
<TableCell className="text-right">
<Dialog
open={open && edit === user.id}
onOpenChange={(isOpen) => {
setOpen(isOpen);
if (!isOpen) {
setEdit(null);
form.reset();
}
}}
>
<DialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="cursor-pointer"
onClick={() => {
setOpen(true);
setEdit(user.id);
}}
>
<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>
</Table>
</div>

View File

@@ -47,4 +47,6 @@ export const API_URLS = {
Update_Pyment_Type: (id: number) =>
`${API_V}admin/product/${id}/update_payment_type/`,
Import_Balance: `${API_V}admin/product/import/balance/`,
PriceTypeSet: (id: number | string) =>
`${API_V}admin/user/${id}/set_price_type/`,
};