delete super admin

This commit is contained in:
Samandar Turgunboyev
2025-12-12 16:39:32 +05:00
parent 92151f4b98
commit 18e43f0eb8
20 changed files with 267 additions and 110 deletions

32
package-lock.json generated
View File

@@ -43,7 +43,8 @@
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"zod": "^4.1.13" "zod": "^4.1.13",
"zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",
@@ -6156,6 +6157,35 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
},
"node_modules/zustand": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
"integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View File

@@ -47,7 +47,8 @@
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"zod": "^4.1.13" "zod": "^4.1.13",
"zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",

View File

@@ -1,8 +1,26 @@
import { user_api } from "@/shared/config/api/user/api";
import { userStore } from "@/shared/hooks/user";
import { SidebarProvider, SidebarTrigger } from "@/shared/ui/sidebar"; import { SidebarProvider, SidebarTrigger } from "@/shared/ui/sidebar";
import { AppSidebar } from "@/widgets/sidebar-layout"; import { AppSidebar } from "@/widgets/sidebar-layout";
import React from "react"; import { useQuery } from "@tanstack/react-query";
import React, { useEffect } from "react";
const SidebarLayout = ({ children }: { children: React.ReactNode }) => { const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
const { data } = useQuery({
queryKey: ["get_me"],
queryFn: () => user_api.getMe(),
select(data) {
return data.data.data;
},
});
const { addUser } = userStore();
useEffect(() => {
if (data) {
addUser(data);
}
}, [data]);
return ( return (
<SidebarProvider> <SidebarProvider>
<AppSidebar /> <AppSidebar />

View File

@@ -1,4 +1,5 @@
import type { DistrictListData } from "@/features/districts/lib/data"; import type { DistrictListData } from "@/features/districts/lib/data";
import { userStore } from "@/shared/hooks/user";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
Table, Table,
@@ -30,6 +31,8 @@ const TableDistrict = ({
setEditingDistrict, setEditingDistrict,
currentPage, currentPage,
}: Props) => { }: Props) => {
const { user } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{isLoading && ( {isLoading && (
@@ -79,15 +82,17 @@ const TableDistrict = ({
> >
<Edit className="w-4 h-4" /> <Edit className="w-4 h-4" />
</Button> </Button>
{user?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
onClick={() => handleDelete(d)} onClick={() => handleDelete(d)}
disabled={!user?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
> >
<Trash className="w-4 h-4" /> <Trash className="w-4 h-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { DoctorListResData } from "@/features/doctors/lib/data"; import type { DoctorListResData } from "@/features/doctors/lib/data";
import { userStore } from "@/shared/hooks/user";
import formatPhone from "@/shared/lib/formatPhone"; import formatPhone from "@/shared/lib/formatPhone";
import { Badge } from "@/shared/ui/badge"; import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
@@ -36,6 +37,7 @@ const TableDoctor = ({
handleDelete, handleDelete,
isFetching, isFetching,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{(isLoading || isFetching) && ( {(isLoading || isFetching) && (
@@ -112,14 +114,17 @@ const TableDoctor = ({
> >
<Pencil size={18} /> <Pencil size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
disabled={!getme?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))

View File

@@ -1,4 +1,5 @@
import type { LocationListDataRes } from "@/features/location/lib/data"; import type { LocationListDataRes } from "@/features/location/lib/data";
import { userStore } from "@/shared/hooks/user";
import formatDate from "@/shared/lib/formatDate"; import formatDate from "@/shared/lib/formatDate";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -25,6 +26,7 @@ const LocationTable = ({
setDetailDialog, setDetailDialog,
handleDelete, handleDelete,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Table> <Table>
@@ -72,14 +74,17 @@ const LocationTable = ({
> >
<Eye size={18} /> <Eye size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
className="cursor-pointer" className="cursor-pointer"
disabled={!getme?.is_superuser}
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { LocationListDataRes } from "@/features/location/lib/data"; import type { LocationListDataRes } from "@/features/location/lib/data";
import { userStore } from "@/shared/hooks/user";
import formatDate from "@/shared/lib/formatDate"; import formatDate from "@/shared/lib/formatDate";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -25,6 +26,7 @@ const UserLocationTable = ({
setDetailDialog, setDetailDialog,
handleDelete, handleDelete,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Table> <Table>
@@ -72,14 +74,17 @@ const UserLocationTable = ({
> >
<Eye size={18} /> <Eye size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
disabled={!getme?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { ObjectListData } from "@/features/objects/lib/data"; import type { ObjectListData } from "@/features/objects/lib/data";
import { userStore } from "@/shared/hooks/user";
import { Badge } from "@/shared/ui/badge"; import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -33,6 +34,7 @@ const ObjectTable = ({
isError, isError,
isLoading, isLoading,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{isLoading && ( {isLoading && (
@@ -96,14 +98,17 @@ const ObjectTable = ({
> >
<Pencil size={18} /> <Pencil size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
className="cursor-pointer" className="cursor-pointer"
disabled={!getme?.is_superuser}
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))

View File

@@ -5,6 +5,7 @@ import {
} from "@/features/pharm/lib/data"; } from "@/features/pharm/lib/data";
import AddedPharm from "@/features/pharm/ui/AddedPharm"; import AddedPharm from "@/features/pharm/ui/AddedPharm";
import DeletePharm from "@/features/pharm/ui/DeletePharm"; import DeletePharm from "@/features/pharm/ui/DeletePharm";
import { userStore } from "@/shared/hooks/user";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
Dialog, Dialog,
@@ -28,6 +29,7 @@ import { Edit, Loader2, Plus, Trash } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
const PharmList = () => { const PharmList = () => {
const { user: getme } = userStore();
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [nameFilter, setNameFilter] = useState<string>(""); const [nameFilter, setNameFilter] = useState<string>("");
const limit = 20; const limit = 20;
@@ -140,14 +142,17 @@ const PharmList = () => {
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
disabled={!getme.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(plan)} onClick={() => handleDelete(plan)}
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))

View File

@@ -1,4 +1,5 @@
import type { PharmaciesListData } from "@/features/pharmacies/lib/data"; import type { PharmaciesListData } from "@/features/pharmacies/lib/data";
import { userStore } from "@/shared/hooks/user";
import formatPhone from "@/shared/lib/formatPhone"; import formatPhone from "@/shared/lib/formatPhone";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -29,6 +30,7 @@ const PharmaciesTable = ({
setDialogOpen, setDialogOpen,
handleDelete, handleDelete,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Table> <Table>
@@ -82,14 +84,17 @@ const PharmaciesTable = ({
> >
<Pencil size={18} /> <Pencil size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
disabled={!getme?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -2,6 +2,7 @@ import { pill_api } from "@/features/pill/lib/api";
import { type PillListData, type PillType } from "@/features/pill/lib/data"; import { type PillListData, type PillType } from "@/features/pill/lib/data";
import AddedPill from "@/features/pill/ui/AddedPill"; import AddedPill from "@/features/pill/ui/AddedPill";
import DeletePill from "@/features/pill/ui/DeletePill"; import DeletePill from "@/features/pill/ui/DeletePill";
import { userStore } from "@/shared/hooks/user";
import formatPrice from "@/shared/lib/formatPrice"; import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -26,6 +27,7 @@ import { Edit, Plus, Trash } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
const PillList = () => { const PillList = () => {
const { user: getme } = userStore();
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const limit = 20; const limit = 20;
const [nameFilter, setNameFilter] = useState<string>(""); const [nameFilter, setNameFilter] = useState<string>("");
@@ -123,14 +125,17 @@ const PillList = () => {
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(plan)} onClick={() => handleDelete(plan)}
disabled={!getme.is_superuser}
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { PlanListData } from "@/features/plans/lib/data"; import type { PlanListData } from "@/features/plans/lib/data";
import { userStore } from "@/shared/hooks/user";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
Table, Table,
@@ -33,6 +34,7 @@ const PalanTable = ({
setDialogOpen, setDialogOpen,
handleDelete, handleDelete,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{(isLoading || isFetching) && ( {(isLoading || isFetching) && (
@@ -115,15 +117,19 @@ const PalanTable = ({
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
className="cursor-pointer" className="cursor-pointer"
disabled={plan.comment ? true : false} disabled={
!getme?.is_superuser || plan.comment ? true : false
}
onClick={() => handleDelete(plan)} onClick={() => handleDelete(plan)}
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { RegionListResData } from "@/features/region/lib/data"; import type { RegionListResData } from "@/features/region/lib/data";
import { userStore } from "@/shared/hooks/user";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
Table, Table,
@@ -26,6 +27,7 @@ const RegionTable = ({
setDialogOpen: Dispatch<SetStateAction<boolean>>; setDialogOpen: Dispatch<SetStateAction<boolean>>;
handleDelete: (user: RegionListResData) => void; handleDelete: (user: RegionListResData) => void;
}) => { }) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{isLoading && ( {isLoading && (
@@ -68,14 +70,17 @@ const RegionTable = ({
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
disabled={!getme?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(plan)} onClick={() => handleDelete(plan)}
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -5,6 +5,7 @@ import { type OrderListDataRes } from "@/features/specifications/lib/data";
import { AddedSpecification } from "@/features/specifications/ui/AddedSpecification"; import { AddedSpecification } from "@/features/specifications/ui/AddedSpecification";
import DeleteOrder from "@/features/specifications/ui/DeleteOrder"; import DeleteOrder from "@/features/specifications/ui/DeleteOrder";
import { SpecificationDetail } from "@/features/specifications/ui/SpecificationDetail "; import { SpecificationDetail } from "@/features/specifications/ui/SpecificationDetail ";
import { userStore } from "@/shared/hooks/user";
import formatPrice from "@/shared/lib/formatPrice"; import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -34,6 +35,7 @@ const SpecificationsList = () => {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const limit = 20; const limit = 20;
const { user: getme } = userStore();
const { const {
data: order, data: order,
@@ -154,14 +156,17 @@ const SpecificationsList = () => {
> >
<Pencil size={18} /> <Pencil size={18} />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
size="icon" size="icon"
disabled={!getme?.is_superuser}
variant="destructive" variant="destructive"
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
> >
<Trash2 size={18} /> <Trash2 size={18} />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -1,4 +1,5 @@
import type { PlanTourListDataRes } from "@/features/tour-plan/lib/data"; import type { PlanTourListDataRes } from "@/features/tour-plan/lib/data";
import { userStore } from "@/shared/hooks/user";
import formatDate from "@/shared/lib/formatDate"; import formatDate from "@/shared/lib/formatDate";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { import {
@@ -36,6 +37,7 @@ const TourPlanTable = ({
setDialogOpen, setDialogOpen,
setDetailOpen, setDetailOpen,
}: Props) => { }: Props) => {
const { user: getme } = userStore();
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{(isLoading || isFetching) && ( {(isLoading || isFetching) && (
@@ -107,14 +109,17 @@ const TourPlanTable = ({
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="icon" size="icon"
disabled={!getme?.is_superuser}
className="cursor-pointer" className="cursor-pointer"
onClick={() => handleDelete(plan)} onClick={() => handleDelete(plan)}
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))

View File

@@ -1,5 +1,6 @@
import { user_api } from "@/features/users/lib/api"; import { user_api } from "@/features/users/lib/api";
import type { UserListData, UserListRes } from "@/features/users/lib/data"; import type { UserListData, UserListRes } from "@/features/users/lib/data";
import { userStore } from "@/shared/hooks/user";
import { Button } from "@/shared/ui/button"; import { Button } from "@/shared/ui/button";
import { Checkbox } from "@/shared/ui/checkbox"; import { Checkbox } from "@/shared/ui/checkbox";
import { import {
@@ -43,6 +44,7 @@ const UserTable = ({
currentPage, currentPage,
}: Props) => { }: Props) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { user: getme } = userStore();
const [pendingUserId, setPendingUserId] = useState<number | null>(null); const [pendingUserId, setPendingUserId] = useState<number | null>(null);
// TableHeader checkbox holati // TableHeader checkbox holati
@@ -182,15 +184,17 @@ const UserTable = ({
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </Button>
{getme?.is_superuser && (
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
disabled={sendMessage} disabled={!getme?.is_superuser || sendMessage}
onClick={() => handleDelete(user)} onClick={() => handleDelete(user)}
className="cursor-pointer" className="cursor-pointer"
> >
<Trash className="h-4 w-4" /> <Trash className="h-4 w-4" />
</Button> </Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))

View File

@@ -21,4 +21,5 @@ export const API_URLS = {
SUPPORT: `${API_V}admin/support/list/`, SUPPORT: `${API_V}admin/support/list/`,
DISTRIBUTED: `${API_V}admin/distributed_product/list/`, DISTRIBUTED: `${API_V}admin/distributed_product/list/`,
SEND_MESSAGE: `${API_V}admin/send_message/`, SEND_MESSAGE: `${API_V}admin/send_message/`,
GET_ME: `${API_V}accounts/user/me/`,
}; };

View File

@@ -0,0 +1,11 @@
import httpClient from "@/shared/config/api/httpClient";
import { API_URLS } from "@/shared/config/api/URLs";
import type { GetMeRes } from "@/shared/config/api/user/type";
import type { AxiosResponse } from "axios";
export const user_api = {
async getMe(): Promise<AxiosResponse<GetMeRes>> {
const res = httpClient.get(API_URLS.GET_ME);
return res;
},
};

View File

@@ -0,0 +1,16 @@
export interface GetMeRes {
status: string;
status_code: number;
data: GetMeResData;
}
export interface GetMeResData {
created_at: string;
first_name: string;
id: number;
is_active: boolean;
is_superuser: boolean;
last_name: string;
region: null | number;
telegram_id: null | number;
}

15
src/shared/hooks/user.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { GetMeResData } from "@/shared/config/api/user/type";
import { create } from "zustand";
type State = {
user: GetMeResData | null;
};
type Actions = {
addUser: (user: GetMeResData) => void;
};
export const userStore = create<State & Actions>((set) => ({
user: null,
addUser: (user: GetMeResData | null) => set(() => ({ user })),
}));