diff --git a/package.json b/package.json index b1d4830..df4bd5e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 792a334..d2ca4b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-select': specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -806,6 +809,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.2.6': resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} peerDependencies: @@ -2977,6 +2993,23 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/number': 1.1.1 diff --git a/src/features/districts/lib/api.ts b/src/features/districts/lib/api.ts index ac23179..6d81aea 100644 --- a/src/features/districts/lib/api.ts +++ b/src/features/districts/lib/api.ts @@ -26,4 +26,17 @@ export const categories_api = { const res = await httpClient.delete(`${API_URLS.CategoryDelete(id)}`); return res; }, + + async import_category() { + const res = await httpClient.post(API_URLS.Import_Category); + return res; + }, + + async image_upload({ body, id }: { id: number | string; body: FormData }) { + const res = await httpClient.patch( + API_URLS.Upload_Image_Category(id), + body, + ); + return res; + }, }; diff --git a/src/features/districts/ui/AddCategories.tsx b/src/features/districts/ui/AddCategories.tsx index 5d34a4b..6f87a25 100644 --- a/src/features/districts/ui/AddCategories.tsx +++ b/src/features/districts/ui/AddCategories.tsx @@ -13,6 +13,7 @@ import { Input } from "@/shared/ui/input"; import { Label } from "@/shared/ui/label"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; import { Loader2, Upload } from "lucide-react"; import { type Dispatch, type SetStateAction } from "react"; import { useForm, useWatch } from "react-hook-form"; @@ -22,7 +23,7 @@ import z from "zod"; type FormValues = z.infer; interface Props { - initialValues: CategoryItem | null; + initialValues: CategoryItem | undefined; setDialogOpen: Dispatch>; } @@ -30,48 +31,38 @@ export default function AddDistrict({ initialValues, setDialogOpen }: Props) { const queryClient = useQueryClient(); const { mutate, isPending } = useMutation({ - mutationFn: (body: FormData) => categories_api.create(body), + mutationFn: ({ body, id }: { body: FormData; id: number | string }) => + categories_api.image_upload({ id, body }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["categories"] }); setDialogOpen(false); }, - onError: () => { - toast.error("Kategoriyani qo'shishda xatolik yuz berdi"); - }, - }); - - const { mutate: updateMutate, isPending: isUpdatePending } = useMutation({ - mutationFn: ({ body, id }: { body: FormData; id: string }) => - categories_api.update({ body, id }), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["categories"] }); - setDialogOpen(false); - }, - onError: () => { - toast.error("Kategoriyani qo'shishda xatolik yuz berdi"); + 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 form = useForm({ resolver: zodResolver(addDistrict), defaultValues: { - name_uz: initialValues?.name_uz ?? "", - name_ru: initialValues?.name_ru ?? "", + name_uz: initialValues?.name ?? "", + name_ru: initialValues?.name ?? "", }, }); function onSubmit(values: FormValues) { const formData = new FormData(); - formData.append("name_uz", values.name_uz); - formData.append("name_ru", values.name_ru); if (values.image) { formData.append("image", values.image); } if (initialValues) { - updateMutate({ body: formData, id: initialValues.id }); - } else { - mutate(formData); + mutate({ id: initialValues?.id, body: formData }); } } @@ -83,7 +74,7 @@ export default function AddDistrict({ initialValues, setDialogOpen }: Props) { return (
- ( @@ -117,7 +108,7 @@ export default function AddDistrict({ initialValues, setDialogOpen }: Props) { )} - /> + /> */} - {isPending || isUpdatePending ? ( + {isPending ? ( - ) : initialValues ? ( - "Tahrirlash" ) : ( "Qo'shish" )} diff --git a/src/features/districts/ui/CategoriesList.tsx b/src/features/districts/ui/CategoriesList.tsx index 9fc936b..7f95f18 100644 --- a/src/features/districts/ui/CategoriesList.tsx +++ b/src/features/districts/ui/CategoriesList.tsx @@ -55,6 +55,7 @@ const CategoriesList = () => { >; - dialogOpen: boolean; - setDialogOpen: Dispatch>; - editing: CategoryItem | null; - setEditing: Dispatch>; -} +// interface Props { +// search: string; +// setSearch: Dispatch>; +// dialogOpen: boolean; +// setDialogOpen: Dispatch>; +// editing: CategoryItem | null; +// setEditing: Dispatch>; +// } -const FilterCategory = ({ - dialogOpen, - setDialogOpen, - setEditing, - editing, -}: Props) => { +const FilterCategory = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: () => categories_api.import_category(), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["categories"] }); + toast.success("Kategoriyalar import qilindi", { + 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", + }); + }, + }); return (
- - - - - - - - - {editing ? "Kategoriyani tahrirlash" : "Kategoriya qo‘shish"} - - - - - - +
); }; diff --git a/src/features/districts/ui/TableCategories.tsx b/src/features/districts/ui/TableCategories.tsx index 8e1bed8..dfc653c 100644 --- a/src/features/districts/ui/TableCategories.tsx +++ b/src/features/districts/ui/TableCategories.tsx @@ -1,6 +1,13 @@ +import AddDistrict from "@/features/districts/ui/AddCategories"; import type { CategoryItem } from "@/features/plans/lib/data"; import { API_URLS } from "@/shared/config/api/URLs"; import { Button } from "@/shared/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/shared/ui/dialog"; import { Table, TableBody, @@ -9,8 +16,8 @@ import { TableHeader, TableRow, } from "@/shared/ui/table"; -import { Edit, Loader2, Trash } from "lucide-react"; -import type { Dispatch, SetStateAction } from "react"; +import { Image, Loader2 } from "lucide-react"; +import { useState, type Dispatch, type SetStateAction } from "react"; interface Props { data: CategoryItem[] | []; @@ -19,6 +26,7 @@ interface Props { handleDelete: (user: CategoryItem) => void; setDialogOpen: Dispatch>; setEditingDistrict: Dispatch>; + dialogOpen: boolean; currentPage: number; } @@ -26,10 +34,10 @@ const TableDistrict = ({ data, isError, isLoading, - handleDelete, + dialogOpen, setDialogOpen, - setEditingDistrict, }: Props) => { + const [initialValues, setEditing] = useState(); return (
{isLoading && ( @@ -55,7 +63,8 @@ const TableDistrict = ({ ID Rasmi Nomi (uz) - Nomi (ru) + Kategoriya idsi + Turi Harakatlar @@ -66,34 +75,35 @@ const TableDistrict = ({ {index + 1} {d.name_uz} - {d.name_uz} - {d.name_ru} + {d.name} + {d.category_id ? d.category_id : "----"} + {d.type ? d.type : "----"} - + */} ))} @@ -108,6 +118,18 @@ const TableDistrict = ({ )} + + + + Rasmdi qo'shish + + + + +
); }; diff --git a/src/features/plans/lib/api.ts b/src/features/plans/lib/api.ts index f3efc49..21caa14 100644 --- a/src/features/plans/lib/api.ts +++ b/src/features/plans/lib/api.ts @@ -47,4 +47,16 @@ export const plans_api = { const res = await httpClient.get(`${API_URLS.UnityList}`, { params }); return res; }, + + async exportProduct() { + const res = await httpClient.get(API_URLS.Export_Product, { + responseType: "blob", + }); + return res; + }, + + async importProduct() { + const res = await httpClient.post(API_URLS.Import_Product); + return res; + }, }; diff --git a/src/features/plans/lib/data.ts b/src/features/plans/lib/data.ts index 04cf2ef..218eccf 100644 --- a/src/features/plans/lib/data.ts +++ b/src/features/plans/lib/data.ts @@ -9,30 +9,34 @@ export interface ProductsList { } export interface Product { - id: string; - name_uz: string; - name_ru: string; - is_active: boolean; - image: string; - category: string; - price: number; - description_uz: string; - description_ru: string; - unity: string; - tg_id: string; - code: string; - article: string; - quantity_left: number; - min_quantity: number; - brand: null | string; - return_date: null | string; - expires_date: null | string; - manufacturer: null | string; - volume: string; + id: number; images: { - id: string; + id: number; image: string; }[]; + meansurement: null | number; + inventory_id: null | string; + product_id: string; + code: string; + name: string; + short_name: string; + weight_netto: null | string; + weight_brutto: null | string; + litr: null | string; + box_type_code: null | string; + box_quant: null | string; + groups: { + id: number; + name: string; + image: string; + type: string; + }[]; + state: string; + barcodes: null | string; + article_code: null | string; + marketing_group_code: null | string; + inventory_kinds: { id: number; name: string }[]; + sector_codes: { id: number; code: string }[]; } export interface Category { @@ -46,11 +50,12 @@ export interface Category { } export interface CategoryItem { - id: string; - name_uz: string; - name_ru: string; - image: string; + id: number; + name: string; + image: string | null; order: number; + category_id: null | number; + type: null | string; } export interface UnityList { diff --git a/src/features/plans/ui/ExcelUpload.tsx b/src/features/plans/ui/ExcelUpload.tsx index 34c6cf5..be02e2f 100644 --- a/src/features/plans/ui/ExcelUpload.tsx +++ b/src/features/plans/ui/ExcelUpload.tsx @@ -1,51 +1,40 @@ "use client"; import { Button } from "@/shared/ui/button"; -import { useRef } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { plans_api } from "../lib/api"; +import { Loader2 } from "lucide-react"; +import { toast } from "sonner"; +import type { AxiosError } from "axios"; export default function ExcelUpload() { - const fileInputRef = useRef(null); - - const handleButtonClick = () => { - fileInputRef.current?.click(); - }; - - const handleFileChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - - // Excel format tekshiruvi - const allowedTypes = [ - "application/vnd.ms-excel", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ]; - - if (!allowedTypes.includes(file.type)) { - alert("Iltimos, faqat Excel (.xls, .xlsx) fayl yuklang"); - return; - } - - // 👉 shu yerda backendga yuborasiz - // uploadExcel(file) - }; + const { mutate, isPending } = useMutation({ + mutationFn: () => plans_api.importProduct(), + onSuccess: (res) => { + toast.success(res.data.message, { + richColors: true, + position: "top-center", + }); + }, + onError: (err: AxiosError) => { + const errMessage = (err.response?.data as { message: string }).message; + toast.error(errMessage || "Xatolik yuz berdi", { + richColors: true, + position: "top-center", + }); + }, + }); return ( <> - - ); } diff --git a/src/features/plans/ui/FilterPlans.tsx b/src/features/plans/ui/FilterPlans.tsx index 381516c..7f05aba 100644 --- a/src/features/plans/ui/FilterPlans.tsx +++ b/src/features/plans/ui/FilterPlans.tsx @@ -1,17 +1,12 @@ +import { plans_api } from "@/features/plans/lib/api"; import type { Product } from "@/features/plans/lib/data"; -import AddedPlan from "@/features/plans/ui/AddedPlan"; import ExcelUpload from "@/features/plans/ui/ExcelUpload"; import { Button } from "@/shared/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/shared/ui/dialog"; import { Input } from "@/shared/ui/input"; -import { AlertCircle, Plus } from "lucide-react"; +import { useMutation } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; import type { Dispatch, SetStateAction } from "react"; +import { toast } from "sonner"; interface Props { searchUser: string; @@ -22,33 +17,54 @@ interface Props { setEditingPlan: Dispatch>; } -const FilterPlans = ({ - searchUser, - setSearchUser, - dialogOpen, - setDialogOpen, - setEditingPlan, - editingPlan, -}: Props) => { +const FilterPlans = ({ searchUser, setSearchUser }: Props) => { + const { mutate } = useMutation({ + mutationFn: () => plans_api.exportProduct(), + onSuccess: (response) => { + const blob = new Blob([response.data], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + + const url = window.URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.download = "products.xlsx"; + document.body.appendChild(link); + link.click(); + + link.remove(); + window.URL.revokeObjectURL(url); + }, + onError: (err: AxiosError) => { + const errorData = (err.response?.data as { data: string }).data; + const errorMessage = (err.response?.data as { message: string }).message; + toast.error(errorMessage || errorData || "Xatolik yuz berdi", { + richColors: true, + position: "top-center", + }); + }, + }); + return (
setSearchUser(e.target.value)} /> - - - - - + + + + {/* Excel uchun kerakli shablon @@ -60,10 +76,9 @@ const FilterPlans = ({ xatolik yuz berishi mumkin.

- - + */} - + {/* + */} ); }; diff --git a/src/features/plans/ui/PalanTable.tsx b/src/features/plans/ui/PalanTable.tsx index 247841a..3294514 100644 --- a/src/features/plans/ui/PalanTable.tsx +++ b/src/features/plans/ui/PalanTable.tsx @@ -1,17 +1,8 @@ "use client"; -import { plans_api } from "@/features/plans/lib/api"; import type { Product } from "@/features/plans/lib/data"; import { API_URLS } from "@/shared/config/api/URLs"; -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, @@ -20,12 +11,8 @@ import { TableHeader, TableRow, } from "@/shared/ui/table"; -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 { Eye, Loader2 } from "lucide-react"; import type { Dispatch, SetStateAction } from "react"; -import { toast } from "sonner"; interface Props { products: Product[] | []; @@ -45,59 +32,7 @@ const ProductTable = ({ isError, setEditingProduct, setDetailOpen, - setDialogOpen, - handleDelete, }: Props) => { - const { data } = useQuery({ - queryKey: ["categories"], - queryFn: () => { - return plans_api.categories({ page: 1, page_size: 1000 }); - }, - select(data) { - return data.data.results; - }, - }); - - const { data: unityData } = useQuery({ - queryKey: ["unity"], - queryFn: () => { - return plans_api.unity({ page: 1, page_size: 1000 }); - }, - select(data) { - return data.data.results; - }, - }); - - 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 (
@@ -121,43 +56,39 @@ const ProductTable = ({ ID Rasmi - Nomi (UZ) - Nomi (RU) - Tavsif (UZ) - Tavsif (RU) - Kategoriya - Birligi - Brendi - Narx - Status - Harakatlar + Nomi + Tavsif + Harakatlar {products.map((product, index) => { - const cat = data?.find((c) => c.id === product.category); - const unity = unityData?.find((u) => u.id === product.unity); - return ( {index + 1} + {product.images.length > 0 ? ( + + {product.name} + + ) : ( + + {product.name} + + )} + {product.name} - {product.name_uz} + {product.short_name && product.short_name.slice(0, 15)}... - {product.name_uz} - {product.name_ru} - {product.description_uz.slice(0, 15)}... - {product.description_ru.slice(0, 15)}... - {cat?.name_uz} - {unity?.name_uz} - {product.brand} - {formatPrice(product.price, true)} - Nofaol - + */} - + */} ); diff --git a/src/features/plans/ui/PlanDetail.tsx b/src/features/plans/ui/PlanDetail.tsx index a24121a..ef277c3 100644 --- a/src/features/plans/ui/PlanDetail.tsx +++ b/src/features/plans/ui/PlanDetail.tsx @@ -1,218 +1,225 @@ -import { categories_api } from "@/features/districts/lib/api"; -import type { Product } from "@/features/plans/lib/data"; -import { unity_api } from "@/features/units/lib/api"; -import { API_URLS } from "@/shared/config/api/URLs"; -import { Badge } from "@/shared/ui/badge"; +"use client"; + import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/shared/ui/dialog"; -import { useQuery } from "@tanstack/react-query"; -import { type Dispatch, type SetStateAction } from "react"; +import { ScrollArea } from "@/shared/ui/scroll-area"; +import type { Product } from "../lib/data"; +import { Badge } from "@/shared/ui/badge"; +import { API_URLS } from "@/shared/config/api/URLs"; -interface Props { - setDetail: Dispatch>; - detail: boolean; - plan: Product | null; +interface ProductDetailModalProps { + product: Product | null; + isOpen: boolean; + onClose: () => void; } -const PlanDetail = ({ detail, setDetail, plan }: Props) => { - const { data } = useQuery({ - queryKey: ["categories"], - queryFn: () => - categories_api.list({ - page: 1, - page_size: 999, - }), - select(data) { - return data.data; - }, - }); - - const { data: unity } = useQuery({ - queryKey: ["unity_list"], - queryFn: () => - unity_api.list({ - page: 1, - page_size: 999, - }), - select(data) { - return data.data; - }, - }); - - if (!plan) return null; +export function PlanDetail({ + product, + isOpen, + onClose, +}: ProductDetailModalProps) { + if (!product) return null; return ( - - - - - {plan.name_uz} - - + + + +
+ + {product.name} + -
- {/* Product Images */} -
- {plan.image && ( - {plan.name_uz} - )} - {plan.images.map((img) => ( - {plan.name_uz} - ))} -
+
+ {/* Images Section */} +
+

Rasmlar

+ {product.images && product.images.length > 0 ? ( +
+ {product.images.map((img) => ( +
+ {product.name} +
+ ))} +
+ ) : ( +
+ Rasm yo'q +
+ )} +
- {/* Product Status */} -
- - {plan.is_active ? "Faol" : "Nofaol"} - - {plan.quantity_left <= plan.min_quantity && ( - Kam qoldi - )} -
+ {/* Product Details */} +
+
+ +

{product.product_id}

+
- {/* Basic Information */} -
-
-

Nomi (O'zbekcha):

-

{plan.name_uz}

-
-
-

Nomi (Ruscha):

-

{plan.name_ru}

-
-
+
+ +

{product.code}

+
- {/* Descriptions */} -
-
-

- Tavsifi (O'zbekcha): -

-

{plan.description_uz}

-
-
-

Tavsifi (Ruscha):

-

{plan.description_ru}

-
-
+
+ +

{product.short_name || "—"}

+
- {/* Price and Category */} -
-
-

Narxi:

-

- {plan.price.toLocaleString()} so'm -

-
-
-

Kategoriya:

-

- {data?.results.find((e) => e.id === plan.category)?.name_uz} -

-
-
-

O'lchov birligi:

-

- {unity?.results.find((e) => e.id === plan.unity)?.name_uz} -

-
-
+
+ + + {product.state} + +
- {/* Product Details */} -
-
-

Kod:

-

{plan.code}

+ {product.article_code && ( +
+ +

{product.article_code}

+
+ )} +
-
-

Artikul:

-

{plan.article}

+ + {/* Weight and Size Info */} +
+ {product.weight_netto && ( +
+ +

{product.weight_netto} kg

+
+ )} + {product.weight_brutto && ( +
+ +

{product.weight_brutto} kg

+
+ )} + {product.litr && ( +
+ +

{product.litr} L

+
+ )} + {product.meansurement && ( +
+ +

{product.meansurement}

+
+ )}
- {plan.brand && ( -
-

Brand:

-

{plan.brand}

+ + {/* Box Info */} + {(product.box_type_code || product.box_quant) && ( +
+ {product.box_type_code && ( +
+ +

{product.box_type_code}

+
+ )} + {product.box_quant && ( +
+ +

{product.box_quant}

+
+ )}
)} - {plan.manufacturer && ( -
-

- Ishlab chiqaruvchi: + + {/* Groups */} + {product.groups && product.groups.length > 0 && ( +

+

Guruhlari

+
+ {product.groups.map((group) => ( + + {group.name} + + ))} +
+
+ )} + + {/* Inventory Kinds */} + {product.inventory_kinds && product.inventory_kinds.length > 0 && ( +
+

Omborxona turlari

+
+ {product.inventory_kinds.map((kind) => ( + + {kind.name} + + ))} +
+
+ )} + + {/* Sector Codes */} + {product.sector_codes && product.sector_codes.length > 0 && ( +
+

Sektor kodlari

+
+ {product.sector_codes.map((sector) => ( +
+ {sector.code} +
+ ))} +
+
+ )} + + {/* Barcodes */} + {product.barcodes && ( +
+ +

+ {product.barcodes}

-

{plan.manufacturer}

)} -
-

Hajm:

-

{plan.volume}

-
- - {/* Quantity Information */} -
-
-

Qolgan miqdor:

-

- {plan.quantity_left} -

-
-
-

Minimal miqdor:

-

- {plan.min_quantity} -

-
-
- - {/* Dates */} - {(plan.return_date || plan.expires_date) && ( -
- {plan.return_date && ( -
-

- Qaytarish sanasi: -

-

- {new Date(plan.return_date).toLocaleDateString("uz-UZ")} -

-
- )} - {plan.expires_date && ( -
-

- Amal qilish muddati: -

-

- {new Date(plan.expires_date).toLocaleDateString("uz-UZ")} -

-
- )} -
- )} -
+
); -}; - -export default PlanDetail; +} diff --git a/src/features/plans/ui/ProductList.tsx b/src/features/plans/ui/ProductList.tsx index bdf5fb5..e7d8ea3 100644 --- a/src/features/plans/ui/ProductList.tsx +++ b/src/features/plans/ui/ProductList.tsx @@ -3,10 +3,10 @@ import type { Product } from "@/features/plans/lib/data"; import DeletePlan from "@/features/plans/ui/DeletePlan"; import FilterPlans from "@/features/plans/ui/FilterPlans"; import PalanTable from "@/features/plans/ui/PalanTable"; -import PlanDetail from "@/features/plans/ui/PlanDetail"; import Pagination from "@/shared/ui/pagination"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; +import { PlanDetail } from "./PlanDetail"; const ProductList = () => { const [currentPage, setCurrentPage] = useState(1); @@ -51,7 +51,11 @@ const ProductList = () => { setSearchUser={setSearchUser} /> - + setDetail(false)} + />
0 ? parts.join(" ") : user.username; }; + const form = useForm>({ + resolver: zodResolver(passwordSet), + defaultValues: { + password: "", + }, + }); + + const [edit, setEdit] = useState(null); + const [open, setOpen] = useState(false); + + const queryClient = useQueryClient(); + + const { mutate } = useMutation({ + mutationFn: ({ + id, + body, + }: { + id: number | string; + body: { password: string }; + }) => user_api.password_set({ body, id }), + onSuccess: () => { + setOpen(false); + setEdit(null); + queryClient.refetchQueries({ queryKey: ["user_list"] }); + toast.success("Parol 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", + }); + }, + }); + + function onSubmit(value: z.infer) { + if (edit) { + mutate({ body: { password: value.password }, id: edit }); + } + } + return ( @@ -109,33 +183,60 @@ export function UserCard({ ID: {user.id} - {/* Uncommnet qilish uchun tugmalar
- - + + + + + + + Parolni qo'yish + +
+ + + ( + + + + + + + + )} + /> +
+ +
+ + +
+
+
- */}
); diff --git a/src/shared/config/api/URLs.ts b/src/shared/config/api/URLs.ts index f76bcf4..e98fede 100644 --- a/src/shared/config/api/URLs.ts +++ b/src/shared/config/api/URLs.ts @@ -34,4 +34,11 @@ export const API_URLS = { QuestionnaireList: `${API_V}admin/questionnaire/list/`, Import_User: `${API_V}admin/user/import_users/`, Refresh_Token: `${API_V}accounts/refresh/token/`, + Export_Product: `${API_V}admin/product/export/`, + Import_Product: `${API_V}admin/product/import/`, + Import_Category: `${API_V}admin/category/import/`, + Upload_Image_Category: (id: number | string) => + `${API_V}admin/category/${id}/upload_image/`, + PasswordSet: (id: number | string) => + `${API_V}admin/user/${id}/set_password/`, }; diff --git a/src/shared/ui/scroll-area.tsx b/src/shared/ui/scroll-area.tsx new file mode 100644 index 0000000..5195f85 --- /dev/null +++ b/src/shared/ui/scroll-area.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; + +import { cn } from "@/shared/lib/utils"; + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ); +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +export { ScrollArea, ScrollBar }; diff --git a/vite.config.ts b/vite.config.ts index e491418..3ee6e79 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }], }, server: { - port: 3000, + port: 3002, open: true, }, });