Merge pull request #2 from more-tech4-magnum-opus/clansAdmin

Clans admin
This commit is contained in:
Ilia vasilenko 2022-10-09 02:37:27 +03:00 committed by GitHub
commit f9b70590c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 808 additions and 91 deletions

3
public/gear.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.3151 8.77952L13.234 7.90445C13.2852 7.60756 13.3116 7.30441 13.3116 7.00127C13.3116 6.69812 13.2852 6.39497 13.234 6.09808L14.3151 5.22302C14.3967 5.15693 14.455 5.06891 14.4825 4.97066C14.5099 4.8724 14.5051 4.76858 14.4686 4.67298L14.4538 4.63235C14.1562 3.84484 13.7105 3.1148 13.1383 2.47752L13.1086 2.4447C13.0392 2.36743 12.9467 2.31189 12.8432 2.28539C12.7398 2.25889 12.6303 2.26268 12.5292 2.29625L11.1873 2.74785C10.6922 2.36345 10.1392 2.0603 9.54173 1.84778L9.2826 0.519567C9.26305 0.419626 9.21184 0.327683 9.13578 0.255952C9.05971 0.184221 8.9624 0.136098 8.85675 0.117977L8.81219 0.110164C7.95225 -0.0367213 7.04775 -0.0367213 6.18781 0.110164L6.14325 0.117977C6.0376 0.136098 5.94029 0.184221 5.86422 0.255952C5.78816 0.327683 5.73695 0.419626 5.7174 0.519567L5.45662 1.85403C4.86388 2.06659 4.3119 2.36958 3.82257 2.75097L2.47076 2.29625C2.36969 2.26241 2.26014 2.25849 2.15666 2.285C2.05317 2.31152 1.96066 2.36722 1.89142 2.4447L1.86171 2.47752C1.2902 3.11525 0.844599 3.84517 0.546219 4.63235L0.531364 4.67298C0.457089 4.86831 0.518159 5.08707 0.684865 5.22302L1.77918 6.10746C1.72802 6.40123 1.70326 6.70125 1.70326 6.9997C1.70326 7.29972 1.72802 7.59975 1.77918 7.89195L0.684865 8.77639C0.603321 8.84248 0.544959 8.9305 0.517539 9.02875C0.490119 9.12701 0.494941 9.23083 0.531364 9.32643L0.546219 9.36706C0.844969 10.1546 1.28732 10.8812 1.86171 11.5219L1.89142 11.5547C1.96083 11.632 2.05335 11.6875 2.15677 11.714C2.26019 11.7405 2.36966 11.7367 2.47076 11.7032L3.82257 11.2484C4.31443 11.6313 4.86407 11.9344 5.45662 12.1454L5.7174 13.4798C5.73695 13.5798 5.78816 13.6717 5.86422 13.7435C5.94029 13.8152 6.0376 13.8633 6.14325 13.8814L6.18781 13.8892C7.05565 14.0369 7.94435 14.0369 8.81219 13.8892L8.85675 13.8814C8.9624 13.8633 9.05971 13.8152 9.13578 13.7435C9.21184 13.6717 9.26305 13.5798 9.2826 13.4798L9.54173 12.1516C10.139 11.9397 10.695 11.6355 11.1873 11.2516L12.5292 11.7032C12.6303 11.737 12.7399 11.7409 12.8433 11.7144C12.9468 11.6879 13.0393 11.6322 13.1086 11.5547L13.1383 11.5219C13.7127 10.8797 14.155 10.1546 14.4538 9.36706L14.4686 9.32643C14.5429 9.13423 14.4818 8.91546 14.3151 8.77952V8.77952ZM12.0621 6.28247C12.1034 6.51842 12.1249 6.76063 12.1249 7.00283C12.1249 7.24503 12.1034 7.48724 12.0621 7.72319L11.9532 8.3498L13.1862 9.3483C12.9992 9.75597 12.7633 10.1419 12.483 10.4984L10.9513 9.98429L10.433 10.3874C10.0386 10.6937 9.5995 10.9344 9.12414 11.1031L8.49528 11.3266L8.19983 12.8423C7.73367 12.8923 7.26303 12.8923 6.79686 12.8423L6.50142 11.3234L5.87751 11.0969C5.4071 10.9281 4.9697 10.6875 4.57852 10.3828L4.06025 9.97804L2.51863 10.4968C2.23804 10.139 2.00366 9.75302 1.81549 9.34674L3.06166 8.33886L2.95438 7.71382C2.91476 7.48099 2.89331 7.24035 2.89331 7.00283C2.89331 6.76375 2.91311 6.52467 2.95438 6.29184L3.06166 5.6668L1.81549 4.65892C2.00201 4.25108 2.23804 3.86667 2.51863 3.50884L4.06025 4.02762L4.57852 3.62291C4.9697 3.3182 5.4071 3.07756 5.87751 2.9088L6.50307 2.68534L6.79851 1.16649C7.26232 1.11648 7.73603 1.11648 8.20149 1.16649L8.49693 2.68222L9.1258 2.90567C9.5995 3.07443 10.0402 3.31507 10.4347 3.62134L10.953 4.0245L12.4847 3.5104C12.7653 3.86824 12.9996 4.2542 13.1878 4.66048L11.9548 5.65899L12.0621 6.28247ZM7.50165 4.09638C5.89731 4.09638 4.59668 5.32771 4.59668 6.84657C4.59668 8.36542 5.89731 9.59676 7.50165 9.59676C9.10599 9.59676 10.4066 8.36542 10.4066 6.84657C10.4066 5.32771 9.10599 4.09638 7.50165 4.09638ZM8.80889 8.08416C8.63743 8.24694 8.43367 8.37603 8.20932 8.46399C7.98498 8.55195 7.74447 8.59705 7.50165 8.59669C7.00814 8.59669 6.54433 8.41387 6.19441 8.08416C6.02246 7.92183 5.88611 7.72893 5.7932 7.51654C5.70029 7.30414 5.65265 7.07645 5.65303 6.84657C5.65303 6.37935 5.84615 5.94026 6.19441 5.60898C6.54433 5.27771 7.00814 5.09645 7.50165 5.09645C7.99517 5.09645 8.45897 5.27771 8.80889 5.60898C8.98084 5.7713 9.11719 5.96421 9.2101 6.1766C9.30301 6.38899 9.35065 6.61668 9.35027 6.84657C9.35027 7.31379 9.15716 7.75288 8.80889 8.08416Z" fill="#1890FF"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

10
public/respect.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" width="14" height="14" rx="7" fill="url(#paint0_linear_31_10595)"/>
<path d="M5.5 3.00005H9.53316C9.62502 2.99894 9.71616 3.01622 9.80124 3.05085C9.88631 3.08549 9.9636 3.13679 10.0286 3.20174C10.0935 3.2667 10.1448 3.34399 10.1795 3.42907C10.2141 3.51414 10.2314 3.6053 10.2303 3.69715V4.92535H7.38379V6.07058H10.2303V7.96268H7.38379V11H5.5V3.00005Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_31_10595" x1="0.5" y1="14" x2="15.4243" y2="12.9335" gradientUnits="userSpaceOnUse">
<stop stop-color="#8637E9"/>
<stop offset="1" stop-color="#D33BE0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 705 B

View File

@ -1,7 +1,7 @@
.addProduct{ .addProduct{
padding-top: 200px; padding-top: 200px;
width: 100%; width: 100%;
height: 100vh; min-height: 100vh;
background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%); background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -35,6 +35,10 @@ export const AddAdminMarketProduct:React.FC = () =>{
return( return(
<div className="addProduct"> <div className="addProduct">
<Header links={[ <Header links={[
{
link:"/admin/users",
name:"Участники"
},
{ {
link:"/admin/market", link:"/admin/market",
name:"Market place" name:"Market place"
@ -43,10 +47,6 @@ export const AddAdminMarketProduct:React.FC = () =>{
link:"/admin/market/add", link:"/admin/market/add",
name:"Cоздать товар (NFT)" name:"Cоздать товар (NFT)"
}, },
{
link:"/admin/users",
name:"Участники"
}
]} ]}
name={user.name}></Header> name={user.name}></Header>
<div className="addProductCard"> <div className="addProductCard">

View File

@ -0,0 +1,88 @@
import { Button, Input } from "antd";
import { Dropdown, Menu, Space } from 'antd';
import React, { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { fetchAddEmployer, fetchChangeEmployer, fetchDelEmployer, getEmployerByTg } from "../../app/admin/adminSlice";
import { RootAdminState } from "../../app/adminStore";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import "../adminUserCard/adminUserCard.css"
export const AddUser:React.FC = () => {
let dispatch = useAppDispatch()
let navigate = useNavigate()
const [type, setType] = useState("WORKER") //WORKER HR ADMIN
const [salary, setSalary] = useState("10")
const [tg, setTg] = useState("")
const [pass, setPass] = useState("")
const [name, setName] = useState("")
const [respect, setrespect] = useState("100")
const [command, setcommand] = useState("")
const onSave = () =>{
console.log(type)
fetchAddEmployer(dispatch,{
name:name,
salary:salary,
respect: respect,
type: type,
pass:pass,
tg:tg,
command: command
})
alert("Пользователь добавлен!")
navigate("/admin/users")
}
return(
<div className="userCard">
<div className="changeProductCard">
<div className="addProductH1">
Создание пользователя
</div>
<div className="userCardWrapper">
<div className="InpWrapper">
<div className="nameText">Telegram</div>
<Input value={tg} onChange={(e)=>setTg(e.target.value)} placeholder="Telegram"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Роль</div>
<select onChange={(e)=>setType(e.target.value)}>
<option>WORKER</option>
<option>HR</option>
<option>ADMIN</option>
</select>
</div>
<div className="InpWrapper">
<div className="nameText">ФИО</div>
<Input value={name} onChange={(e)=>setName(e.target.value)} placeholder="ФИО"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Пароль</div>
<Input value={pass} onChange={(e)=>setPass(e.target.value)} placeholder="ФИО"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Зарплата</div>
<Input type="number" value={salary} onChange={(e)=>setSalary(e.target.value)} placeholder="баланс"></Input>
</div>
<div className="InpWrapper">
<div className="nameText"> Респект</div>
<Input type="number" value={respect} onChange={(e)=>setrespect(e.target.value)} placeholder="респект"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Команда</div>
<Input type="number" value={command} onChange={(e)=>setcommand(e.target.value)} placeholder="команда"></Input>
</div>
</div>
<div className="btnWrapper">
<Button className="btn2" onClick={()=>onSave()}>Добавить</Button>
</div>
</div>
</div>
);
}

View File

View File

@ -0,0 +1,101 @@
import "./adminClans.css"
import { Button, Space, Table, Tag } from 'antd';
import { useNavigate, useParams } from "react-router-dom";
import { getClanByName, getEmployers } from "../../app/admin/adminSlice";
import { useAppSelector } from "../../app/hooks";
import type { ColumnsType } from 'antd/es/table';
import { RootAdminState } from "../../app/adminStore";
interface DataTypeIE{
key:string,
name:string,
telegram:string,
respect:number,
balance:number,
speciality: string,
}
export const AdminClans:React.FC = () =>{
let {name} = useParams()
let clan = useAppSelector((state)=>getClanByName(state, name as string))
let navigator = useNavigate()
const collator = new Intl.Collator('ru');
let users = useAppSelector((state:RootAdminState)=>getEmployers(state))
const data:DataTypeIE[] = []
const columns: ColumnsType<DataTypeIE> = [
{
title: "+",
key: "action",
render: (_, record) =>(
<img style={{cursor:"pointer"}} onClick={()=>navigator("/admin/users/" + record.key)} src="/gear.svg"></img>
)
},
{
title: 'ФИО',
dataIndex: 'name',
key: 'name',
defaultSortOrder: 'descend',
sorter: (a, b) => collator.compare(a.name, b.name) ,
},
{
title: 'Telegram',
dataIndex: 'telegram',
key: 'telegram',
sorter: (a, b) => collator.compare(a.telegram, b.telegram) ,
},
{
title: 'Респект',
dataIndex: 'respect',
key: 'respect',
sorter: (a, b) => a.respect - b.respect,
render: text=><div>{text + " "} <img src="/respect.svg"></img></div>
},
{
title: 'Баланс',
dataIndex: 'balance',
key: 'balance',
sorter: (a, b) => a.balance - b.balance,
render: text=><div>{text + " "} <img src="/rub.svg"></img></div>
},
{
title: 'Специальность',
dataIndex: 'speciality',
key: 'speciality',
sorter: (a, b) => collator.compare(a.speciality, b.speciality) ,
render: text=><Tag color="green" key={text}>
{text}
</Tag>
},
]
if (users.length > 0){
users.forEach((user)=>{
data.push(
{
name: user.name,
balance: user.balance,
command: user.command,
speciality: user.jobTittle,
key: user.telegramID,
telegram: user.telegramID,
respect: user.respect,
} as DataTypeIE
)
})
}
return(
<div className="userTable">
<div className="changeProductCard">
<div className="addProductH1">Участники клана {name}</div>
<Table columns={columns} dataSource={data} />
<Button onClick={()=>navigator("/admin/users")} className="btn2">Назад</Button>
</div>
</div>
);
}

View File

@ -19,7 +19,7 @@
.market{ .market{
padding-top: 200px; padding-top: 200px;
width: 100%; width: 100%;
height: 100vh; min-height: 100vh;
background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%); background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,23 +1,18 @@
import React, { useState } from "react" import React, { useState } from "react"
import { getProducts, getUser } from "../../app/admin/adminSlice"; import { addProduct, addProducts, fetchAddProduct, getProducts, getUser } from "../../app/admin/adminSlice";
import { RootAdminState } from "../../app/adminStore"; import { RootAdminState } from "../../app/adminStore";
import { useAppSelector } from "../../app/hooks"; import { adminFetcher, useAppDispatch, useAppSelector } from "../../app/hooks";
import { Header } from "../../components/Header"; import { Header } from "../../components/Header";
import { AdminMarketCard } from "../adminMarketCard"; import { AdminMarketCard } from "../adminMarketCard";
import "./adminMarket.css" import "./adminMarket.css"
import { import { ProductIE } from "../../app/interfaces";
BrowserRouter as Router,
Route,
Routes,
Link
} from "react-router-dom";
import { AdminMarketPopUp } from "../adminMarketPopUp";
export const AdminMarket:React.FC = () =>{ export const AdminMarket:React.FC = () =>{
const [opened, setOpened] = useState(false); const [first, setFirst] = useState(true)
let user = useAppSelector((state:RootAdminState)=>getUser(state)) let user = useAppSelector((state:RootAdminState)=>getUser(state))
let cards: JSX.Element[] = [] let cards: JSX.Element[] = []
let products = useAppSelector( let prod = useAppSelector(
(state: RootAdminState)=>getProducts(state)).forEach( (state: RootAdminState)=>getProducts(state)).forEach(
product=>cards.push(<AdminMarketCard product=>cards.push(<AdminMarketCard
name={product.name} name={product.name}
@ -26,10 +21,37 @@ export const AdminMarket:React.FC = () =>{
cost={product.cost} cost={product.cost}
id={product.id} id={product.id}
></AdminMarketCard>)) ></AdminMarketCard>))
let products = useAppSelector((state:RootAdminState)=>getProducts(state))
let dispatch = useAppDispatch()
if (products.length == 0 && first ){
setFirst(false)
adminFetcher.get("marketplace/product/").then(
(response)=>{
dispatch(addProducts(
response.data.map((params:any)=>
({
name: params.name,
description:params.description,
image: params.image_cropped,
cost:Number(params.price),
id:params.slug
} as ProductIE)
)
))
}
)
}
return( return(
<div className="market"> <div className="market">
<Header links={[ <Header links={[
{
link:"/admin/users",
name:"Участники"
},
{ {
link:"/admin/market", link:"/admin/market",
name:"Market place" name:"Market place"
@ -38,10 +60,6 @@ export const AdminMarket:React.FC = () =>{
link:"/admin/market/add", link:"/admin/market/add",
name:"Cоздать товар (NFT)" name:"Cоздать товар (NFT)"
}, },
{
link:"/admin/users",
name:"Участники"
}
]} ]}
name={user.name}></Header> name={user.name}></Header>
<div className="marketCard"> <div className="marketCard">

View File

@ -1,6 +1,6 @@
.adminCard{ .adminCard{
width: 240px; width: 240px;
max-height: 400px; max-height: 450px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #FFFFFF; background: #FFFFFF;
@ -37,7 +37,7 @@
.adminCardH2{ .adminCardH2{
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 18px;
color: rgba(0, 0, 0, 0.85) color: rgba(0, 0, 0, 0.85)
} }

View File

@ -14,15 +14,18 @@ export const AdminMarketCard:React.FC<ProductIE> = (props) =>{
return( return(
<div className="adminCard"> <div className="adminCard">
<img src={props.image}></img> <img className="adminImg" src={props.image}></img>
<div className="adminCost"> <div className="adminCardWrapper">
<img src="/rub.svg"></img> <div className="adminCost">
<div>{props.cost}</div> <img src="/rub.svg"></img>
<div>{props.cost}</div>
</div>
<div className="adminCardH2">{props.name}</div>
<div className="adminCardDescr">{props.description.split(" ").slice(0,6).join(" ")}</div>
<Button className="btn1" style={{width:"100%"}} onClick={()=>navigate("/admin/market/" + props.id) }>Редактировать</Button>
<Button className="btn2" style={{width:"100%"}} onClick={()=>fetchDelProduct(dispatch, props.id)}>Удалить</Button>
</div> </div>
<div>{props.name}</div>
<div>{props.description}</div>
<Button className="btn1" onClick={()=>navigate("/admin/market/" + props.id) }>Редактировать</Button>
<Button className="btn2" onClick={()=>fetchDelProduct(dispatch, props.id)}>Удалить</Button>
</div> </div>
) )
} }

View File

@ -1,7 +1,7 @@
.changeProduct{ .changeProduct{
padding-top: 200px; padding-top: 200px;
width: 100%; width: 100%;
height: 100vh; min-height: 100vh;
background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%); background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -11,7 +11,7 @@ import { Header } from "../../components/Header"
export const AdminMarketPopUp:React.FC = () =>{ export const AdminMarketPopUp:React.FC = () =>{
let {id} = useParams() let {id} = useParams()
let product = useAppSelector((state: RootAdminState)=>getProductByID(state, Number(id))) let product = useAppSelector((state: RootAdminState)=>getProductByID(state, id as string))
const [cost, setCost] = useState(product.cost) const [cost, setCost] = useState(product.cost)
const [name, setName] = useState(product.name) const [name, setName] = useState(product.name)
const [descr, setDescr] = useState(product.description) const [descr, setDescr] = useState(product.description)
@ -23,10 +23,9 @@ export const AdminMarketPopUp:React.FC = () =>{
fetchChangeProduct(dispatch, { fetchChangeProduct(dispatch, {
cost: cost, cost: cost,
name: name, name: name,
description: descr, descr: descr,
id:product.id, id: product.id
image: product.image })
} as ProductIE)
navigate("/admin/market") navigate("/admin/market")
} }

View File

@ -0,0 +1,34 @@
.userCard{
padding-top: 200px;
width: 100%;
min-height: 100vh;
background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%);
display: flex;
flex-direction: column;
align-items: center;
}
.userCardWrapper{
display: flex;
flex-direction: column;
gap:15px;
justify-content: flex-start;
align-items: flex-start;
}
.nameText{
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, 0.85)
}
.fieldText{
font-size: 14px;
line-height: 22px;
color: rgba(0, 0, 0, 0.45);
}
.btnWrapper{
display: flex;
flex-direction: row;
gap: 25px
}

View File

@ -0,0 +1,84 @@
import { Button, Input } from "antd";
import React, { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { fetchChangeEmployer, fetchDelEmployer, getEmployerByTg } from "../../app/admin/adminSlice";
import { RootAdminState } from "../../app/adminStore";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import "./adminUserCard.css"
export const AdminUserCard:React.FC = () => {
let {tg} = useParams()
let user = useAppSelector((state: RootAdminState) => getEmployerByTg(state, tg as string))
let dispatch = useAppDispatch()
let navigate = useNavigate()
const [name, setName] = useState(user.name)
const [balance, setbalance] = useState(user.balance)
const [respect, setrespect] = useState(user.respect)
const [command, setcommand] = useState(user.name)
const [jobTittle, setjobTittle] = useState(user.jobTittle)
const onDelete = () =>{
fetchDelEmployer(dispatch, user.telegramID)
}
const onSave = () =>{
fetchChangeEmployer(dispatch,{
name:name,
balance:balance,
respect: respect,
speciality: jobTittle,
tg:user.telegramID
})
alert("Данные сохранены!")
navigate("/admin/users")
}
return(
<div className="userCard">
<div className="changeProductCard">
<div className="addProductH1">
Редактирование пользователя: {user.name}
</div>
<div className="userCardWrapper">
<div className="InpWrapper">
<div className="nameText">Telegram</div>
<div className="fieldText">{user.telegramID}</div>
</div>
<div className="InpWrapper">
<div className="nameText">Кошелек</div>
<div className="fieldText">{user.wallet}</div>
</div>
<div className="InpWrapper">
<div className="nameText">Роль</div>
<div className="fieldText">{user.role}</div>
</div>
<div className="InpWrapper">
<div className="nameText">ФИО</div>
<Input value={name} onChange={(e)=>setName(e.target.value)} placeholder="ФИО"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Баланс</div>
<Input value={balance} onChange={(e)=>setbalance(Number(e.target.value))} placeholder="баланс"></Input>
</div>
<div className="InpWrapper">
<div className="nameText"> Респект</div>
<Input value={respect} onChange={(e)=>setrespect(Number(e.target.value))} placeholder="респект"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Команда</div>
<Input value={command} onChange={(e)=>setcommand(e.target.value)} placeholder="команда"></Input>
</div>
<div className="InpWrapper">
<div className="nameText">Специальность</div>
<Input value={jobTittle} onChange={(e)=>setjobTittle(e.target.value)} placeholder="специальность"></Input>
</div>
</div>
<div className="btnWrapper">
<Button className="btn1" onClick={()=>onDelete()}>Удалить пользователя</Button>
<Button className="btn2" onClick={()=>onSave()}>Сохранить</Button>
</div>
</div>
</div>
);
}

View File

@ -8,21 +8,8 @@ import { RootAdminState } from "../app/adminStore";
import { Header } from "../components/Header"; import { Header } from "../components/Header";
export const AdminPage:React.FC = () => { export const AdminPage:React.FC = () => {
const [respProducts, setRespProducts] = useState([-1 as any])
let products = useAppSelector((state:RootAdminState)=>getProducts(state))
let user = useAppSelector((state:RootAdminState)=>getUser(state))
let dispatch = useAppDispatch()
if (products.length == 0){
adminFetcher.get("marketplace/product/").then(
(response)=> setRespProducts(response.data as any)
)
}
if (respProducts[0] != -1){ let user = useAppSelector((state:RootAdminState)=>getUser(state))
respProducts.forEach((product)=>{
fetchAddProduct(dispatch, product)
})
}
return( return(

View File

@ -0,0 +1,225 @@
import { Button, Space, Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useReducer, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { clearScreenDown } from 'readline';
import { getClans, getEmployers, getUser, setClans, setEmployers } from '../../app/admin/adminSlice';
import { RootAdminState } from '../../app/adminStore';
import { adminFetcher, useAppDispatch, useAppSelector } from '../../app/hooks';
import { ClanIE, EmployerIE } from '../../app/interfaces';
import { Header } from '../../components/Header';
import "./userTable.css"
interface DataTypeIE{
key:string,
name:string,
telegram:string,
command:string,
respect:number,
balance:number,
speciality: string,
}
interface ClanColumnsIE{
key: string,
name: string,
tg: string,
members: number
}
export const UserTable:React.FC = () =>{
let [clans, setclans] = useState(false)
let navigator = useNavigate()
const collator = new Intl.Collator('ru');
let user = useAppSelector((state:RootAdminState)=>getUser(state))
const clanColums:ColumnsType<ClanColumnsIE> = [
{
title: "+",
key: "action",
render: (_, record) =>(
<img style={{cursor:"pointer"}} onClick={()=>navigator("/admin/clans/" + record.name)} src="/gear.svg"></img>
)
},
{
title: 'Название клана',
dataIndex: 'name',
key: 'name',
defaultSortOrder: 'descend',
sorter: (a, b) => collator.compare(a.name, b.name) ,
},
{
title: 'Telegram клана',
dataIndex: 'name',
key: 'name',
render: (_, record) => (<div>@{record.name.split(" ").join("")}</div>),
sorter: (a, b) => collator.compare(a.name, b.name) ,
},
{
title: 'Количество участников',
dataIndex: 'members',
key: 'members',
sorter: (a, b) => a.members - b.members ,
},
{
title: 'Участники',
dataIndex: 'members',
key: 'members',
render: (_, record) => (<Link to={"/admin/clans/" + record.name} ><div style={{color:"#1890FF"}}>Посмотреть</div></Link>),
sorter: (a, b) => collator.compare(a.name, b.name) ,
},
]
const columns: ColumnsType<DataTypeIE> = [
{
title: "+",
key: "action",
render: (_, record) =>(
<img style={{cursor:"pointer"}} onClick={()=>navigator("/admin/users/" + record.key)} src="/gear.svg"></img>
)
},
{
title: 'ФИО',
dataIndex: 'name',
key: 'name',
defaultSortOrder: 'descend',
sorter: (a, b) => collator.compare(a.name, b.name) ,
},
{
title: 'Telegram',
dataIndex: 'telegram',
key: 'telegram',
sorter: (a, b) => collator.compare(a.telegram, b.telegram) ,
},
{
title: 'Название клана',
dataIndex: 'command',
key: 'command',
sorter: (a, b) => Number(a.command) - Number(b.command) ,
},
{
title: 'Респект',
dataIndex: 'respect',
key: 'respect',
sorter: (a, b) => a.respect - b.respect,
render: text=><div>{text + " "} <img src="/respect.svg"></img></div>
},
{
title: 'Баланс',
dataIndex: 'balance',
key: 'balance',
sorter: (a, b) => a.balance - b.balance,
render: text=><div>{text + " "} <img src="/rub.svg"></img></div>
},
{
title: 'Специальность',
dataIndex: 'speciality',
key: 'speciality',
sorter: (a, b) => collator.compare(a.speciality, b.speciality) ,
render: text=><Tag color="green" key={text}>
{text}
</Tag>
},
]
let dispatch = useAppDispatch()
const data:DataTypeIE[] = []
const clanData:ClanColumnsIE[] = []
let users = useAppSelector((state:RootAdminState)=>getEmployers(state))
let clansData = useAppSelector((state:RootAdminState)=>getClans(state))
if (users.length == 0){
adminFetcher.get("users/").then((response)=>{
dispatch(setEmployers(response.data.map(((user:any)=>({
name: user.name,
wallet: user.wallet_public_key,
telegramID: user.telegram,
command: user.command,
role: user.type,
respect: user.respect,
balance: user.salary,
jobTittle: user.department,
clan: user.clan_name
}) as EmployerIE ))))
})
adminFetcher.get("season/clans/").then((response:any)=>{
dispatch(setClans(
response.data.map((clan:any)=>({
name: clan.name,
users: clan.users.map(((user:any)=>({
name: user.name,
wallet: user.wallet_public_key,
telegramID: user.telegram,
command: user.command,
role: user.type,
respect: user.respect,
balance: user.salary,
jobTittle: user.department,
}) ))
} as ClanIE))
))
})
}
if (users.length > 0){
users.forEach((user)=>{
data.push(
{
name: user.name,
balance: user.balance,
command: user.clan,
speciality: user.jobTittle,
key: user.telegramID,
telegram: user.telegramID,
respect: user.respect,
} as DataTypeIE
)
})
clansData.forEach((clan)=>{
clanData.push({
key:clan.name,
name:clan.name,
tg: clan.name,
members:clan.users.length
} as ClanColumnsIE)
})
}
return(
<div className='userTable'>
<Header links={[
{
link:"/admin/users",
name:"Участники"
},
{
link:"/admin/market",
name:"Market place"
},
{
link:"/admin/market/add",
name:"Cоздать товар (NFT)"
},
]}
name={user.name}></Header>
<div className='changeProductCard'>
<div className="addProductH1">Участники VTB Games</div>
<div className='btnWrapper'>
<Button onClick={()=>setclans(false)} className={clans? "btn1":"btn2"}>Пользователи</Button>
<Button onClick={()=>setclans(true)} className={clans? "btn2":"btn1"}>Кланы</Button>
<Button onClick={()=>navigator("add/")} className='btn1'>Новый пользователь</Button>
<Button onClick={()=>adminFetcher.post("season/")} className='btn1'>Начать сезон</Button>
</div>
{
clans? <Table columns={clanColums} dataSource={clanData} />:<Table columns={columns} dataSource={data} />
}
</div>
</div>
);
}

View File

@ -0,0 +1,17 @@
.userTable{
padding-top: 200px;
width: 100%;
min-height: 100vh;
background: linear-gradient(85.91deg, #096DD9 0%, #40A9FF 100%);
display: flex;
flex-direction: column;
align-items: center;
}
.userTableWrapper{
display: flex;
flex-direction: column;
gap:15px;
justify-content: flex-start;
align-items: flex-start;
}

View File

@ -1,9 +1,10 @@
import { createAsyncThunk, createSlice, createSelector, PayloadAction } from '@reduxjs/toolkit' import { createAsyncThunk, createSlice, createSelector, PayloadAction } from '@reduxjs/toolkit'
import { stat } from 'fs' import { stat } from 'fs'
import { Market, Roles, SortTypes, UserIE, ProductIE} from '../interfaces' import { Market, Roles, SortTypes, UserIE, ProductIE, EmployerIE, ClanIE} from '../interfaces'
import { AppAdminDispatch, RootAdminState } from '../adminStore' import { AppAdminDispatch, RootAdminState } from '../adminStore'
import { adminFetcher, useAppDispatch } from '../hooks' import { adminFetcher, useAppDispatch } from '../hooks'
import { host, token } from '../consts' import { host, token } from '../consts'
import { triggerAsyncId } from 'async_hooks'
@ -13,14 +14,16 @@ const initState = {
user: { user: {
wallet:"123214", wallet:"123214",
balance: 100, balance: 100,
id:1, id:"1",
role:Roles.admin, role:Roles.admin,
name:"Firesieht" name:"Firesieht"
} as UserIE, //потом достается запросом } as UserIE, //потом достается запросом
market: { market: {
sortType: SortTypes.sortByPriceSmaller, sortType: SortTypes.sortByPriceSmaller,
products: [] products: []
} as Market // потом достается запросом } as Market, // потом достается запросом
employers: [] as EmployerIE[],
clans: [] as ClanIE[]
} }
const adminSlice = createSlice( const adminSlice = createSlice(
@ -28,13 +31,42 @@ const adminSlice = createSlice(
name: "adminSlice", name: "adminSlice",
initialState: initState, initialState: initState,
reducers:{ reducers:{
setClans(state, action:PayloadAction<ClanIE[]>){
state.clans = action.payload
},
setEmployers(state, action:PayloadAction<EmployerIE[]>){
state.employers = action.payload
},
addEmployer(state, action:PayloadAction<EmployerIE>){
if(state.employers.length == 0){
state.employers = state.employers.concat([action.payload])
}
},
delEmployer(state, action:PayloadAction<string>){
let employers = state.employers
let ind = 0
employers.forEach((employer, index)=>{
if (employer.telegramID == action.payload){
ind = index
}
})
employers.splice(ind, 1)
state.employers = employers
},
sendCoins(state, action: PayloadAction<number>){ sendCoins(state, action: PayloadAction<number>){
state.user.balance = state.user.balance - action.payload state.user.balance = state.user.balance - action.payload
}, },
addProduct(state, action: PayloadAction<ProductIE>){ addProduct(state, action: PayloadAction<ProductIE>){
state.market.products = state.market.products.concat([action.payload]) if(state.market.products.indexOf(action.payload) == -1){
state.market.products = state.market.products.concat([action.payload])
}
}, },
delProduct(state, action:PayloadAction<number>){ addProducts(state, action: PayloadAction<ProductIE[]>){
if(state.market.products.length == 0){
state.market.products = state.market.products.concat(action.payload)
}
},
delProduct(state, action:PayloadAction<string>){
let products = state.market.products let products = state.market.products
let ind = 0 let ind = 0
products.forEach((product, index)=>{ products.forEach((product, index)=>{
@ -45,6 +77,15 @@ const adminSlice = createSlice(
products.splice(ind, 1) products.splice(ind, 1)
state.market.products = products state.market.products = products
}, },
changeEmployer(state, action:PayloadAction<EmployerIE>){
let employers = state.employers
employers.forEach((product,index)=>{
if (product.telegramID == action.payload.telegramID){
employers[index] = action.payload
}
})
state.employers = employers
},
changeProduct(state, action:PayloadAction<ProductIE>){ changeProduct(state, action:PayloadAction<ProductIE>){
let products = state.market.products let products = state.market.products
products.forEach((product,index)=>{ products.forEach((product,index)=>{
@ -64,29 +105,99 @@ export async function fetchSendCoins(dispatch:AppAdminDispatch, params:{userID:n
dispatch(sendCoins(params.amount)) dispatch(sendCoins(params.amount))
} }
export async function fetchAddProduct(dispatch:AppAdminDispatch, params:{image: FormData, descr: string, name:string, cost:number}) { export async function fetchAddProduct(dispatch:AppAdminDispatch, params:{image: File, descr: string, name:string, cost:number}) {
//тут идет фетч //тут идет фетч
let data = { const formData = new FormData()
image: "", formData.append("name",params.name)
description: params.descr, formData.append("description",params.descr)
name: params.name, formData.append("image", params.image)
cost: params.cost, formData.append("price",params.cost.toString())
id: params.cost adminFetcher.post("marketplace/product/", formData).then((response)=>{
} as ProductIE //vмоковая даата dispatch(addProduct({
adminFetcher.post("marketplace/product/", { name: response.data.name,
description:response.data.description,
image: response.data.image_cropped,
cost:Number(response.data.price),
id:response.data.slug
} as ProductIE))
}) })
dispatch(addProduct(data))
} }
export async function fetchDelProduct(dispatch:AppAdminDispatch, id:number) { export async function fetchDelProduct(dispatch:AppAdminDispatch, id:string) {
//тут идет фетч //тут идет фетч
dispatch(delProduct(id)) adminFetcher.delete("marketplace/product/"+id).then(()=>dispatch(delProduct(id)))
} }
export async function fetchChangeProduct(dispatch:AppAdminDispatch, params:ProductIE) { export async function fetchChangeProduct(dispatch:AppAdminDispatch, params:{id:string, descr: string, name:string, cost:number}) {
dispatch(changeProduct(params)) const formData = new FormData()
formData.append("name",params.name)
formData.append("description",params.descr)
formData.append("price",params.cost.toString())
adminFetcher.patch("marketplace/product/" + params.id, formData).then((response)=>{
dispatch(delProduct(params.id))
dispatch(addProduct({
name: response.data.name,
description:response.data.description,
image: response.data.image_cropped,
cost:Number(response.data.price),
id:response.data.slug
} as ProductIE))
})
}
export async function fetchAddEmployer(dispatch:AppAdminDispatch, params:{pass:string, name: string, respect: string, salary:string, command:string, tg:string, type: string}) {
const formData = new FormData()
formData.append("name",params.name)
formData.append("type",params.type)
formData.append("respect", params.respect.toString())
formData.append("salary",params.salary.toString())
formData.append("telegram",params.tg)
formData.append("command", params.command)
formData.append("password", params.pass)
adminFetcher.post("users/", formData).then((user)=>{
dispatch(addEmployer({
name: user.data.name,
wallet: user.data.wallet_public_key,
telegramID: user.data.telegram,
command: user.data.command,
role: user.data.type,
respect: user.data.respect,
balance: user.data.salary,
jobTittle: user.data.department,
} as EmployerIE))
})
}
export async function fetchDelEmployer(dispatch:AppAdminDispatch, tg:string) {
adminFetcher.delete("users/"+tg).then(()=>dispatch(delProduct(tg)))
}
export async function fetchChangeEmployer(dispatch:AppAdminDispatch, params:{name: string, respect: number, balance:number, speciality:string, tg:string}) {
const formData = new FormData()
formData.append("name",params.name)
formData.append("respect", params.respect.toString())
formData.append("salary",params.balance.toString())
adminFetcher.patch("users/" + params.tg, formData).then((user)=>{
console.log(user)
dispatch(changeEmployer({
name: user.data.name,
wallet: user.data.wallet_public_key,
telegramID: user.data.telegram,
command: user.data.command,
role: user.data.type,
respect: user.data.respect,
balance: user.data.salary,
jobTittle: user.data.department,
} as EmployerIE))
})
} }
export const getProducts = createSelector( export const getProducts = createSelector(
@ -95,7 +206,12 @@ export const getProducts = createSelector(
) )
export const getProductByID = createSelector( export const getProductByID = createSelector(
(state:RootAdminState, id:number) => state.adminSlice.market.products.filter((product)=>product.id == id)[0], (state:RootAdminState, id:string) => state.adminSlice.market.products.filter((product)=>product.id == id)[0],
(field)=>field
)
export const getEmployerByTg = createSelector(
(state:RootAdminState, tg:string) => state.adminSlice.employers.filter((product)=>product.telegramID == tg)[0],
(field)=>field (field)=>field
) )
@ -107,12 +223,30 @@ export const getUser = createSelector(
(state:RootAdminState) => state.adminSlice.user, (state:RootAdminState) => state.adminSlice.user,
(field)=>field (field)=>field
) )
export const getEmployers = createSelector(
(state:RootAdminState) => state.adminSlice.employers,
(field)=>field
)
export const getClans = createSelector(
(state:RootAdminState) => state.adminSlice.clans,
(field)=>field
)
export const getClanByName = createSelector(
(state:RootAdminState, name: string) => state.adminSlice.clans.filter((clan)=>clan.name == name)[0],
(field)=>field
)
export const { export const {
sendCoins, sendCoins,
addProduct, addProduct,
delProduct, delProduct,
changeProduct, changeProduct,
addProducts,
setEmployers,
addEmployer,
delEmployer,
changeEmployer,
setClans
} = adminSlice.actions } = adminSlice.actions
export default adminSlice.reducer export default adminSlice.reducer

View File

@ -9,7 +9,7 @@ export const useAppSelector: TypedUseSelectorHook<RootAdminState> = useSelector;
export const adminFetcher = axios.create( export const adminFetcher = axios.create(
{ {
baseURL: host, baseURL: host,
timeout: 1000, timeout: 5000,
headers: { headers: {
Authorization: 'Bearer ' + token Authorization: 'Bearer ' + token
} }

View File

@ -14,7 +14,6 @@ export enum Specialities{
export interface UserIE{ export interface UserIE{
wallet:string, wallet:string,
id:number
role:Roles role:Roles
balance: number, balance: number,
name:string, name:string,
@ -23,8 +22,11 @@ export interface UserIE{
export interface EmployerIE extends UserIE{ export interface EmployerIE extends UserIE{
jobTittle:Specialities, jobTittle:string,
respect: number, respect: number,
telegramID: string,
command: string,
clan:string
} }
@ -42,7 +44,7 @@ export interface ProductIE{
description:string, description:string,
image: string, image: string,
cost: number, cost: number,
id: number, id: string,
} }
export enum SortTypes{ export enum SortTypes{
@ -55,3 +57,8 @@ export interface Market{
sortType: SortTypes, sortType: SortTypes,
products: ProductIE[], products: ProductIE[],
} }
export interface ClanIE{
name:string,
users:UserIE[]
}

View File

@ -15,6 +15,7 @@
line-height: 24px; line-height: 24px;
overflow: visible; overflow: visible;
color: #FFFFFF; color: #FFFFFF;
z-index: 1000;
} }
.links{ .links{
overflow: visible; overflow: visible;

View File

@ -12,29 +12,25 @@ export const FileUploader:React.FC<FileUploaderIE> = (data) =>{
const props = { const props = {
name: 'file', name: 'file',
action: "",
headers: { headers: {
"content-type": 'multipart/form-data; boundary=----WebKitFormBoundaryqTqJIxvkWFYqvP5s' "content-type": 'multipart/form-data; boundary=----WebKitFormBoundaryqTqJIxvkWFYqvP5s'
}, },
onChange(info:any) { beforeUpload: (file:File) => {
if (info.file.status !== 'uploading') { const isPNG = file.type === 'image/png';
} if (!isPNG) {
message.error(`${file.name} is not a png file`);
if (info.file.status === 'done') {
data.onResponse(info.file.response)
message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
} }
data.onResponse(file)
return isPNG || Upload.LIST_IGNORE;
}, },
}; };
return ( return (
<Upload {...props} customRequest={(file) => { <Upload {...props}>
console.log(file); <Button icon={<UploadOutlined></UploadOutlined>}>Загрузите картинку</Button>
}} onDownload={(file) => {
console.log(file)
}} multiple>
<Button icon={<UploadOutlined></UploadOutlined>}>Загрузите файлы для проверки</Button>
</Upload> </Upload>
); );
} }

View File

@ -17,9 +17,14 @@ import {
Link, Link,
Routes, Routes,
} from "react-router-dom"; } from "react-router-dom";
import { UserTable } from './admin/userTable';
import { AdminUserCard } from './admin/adminUserCard';
import { AdminClans } from './admin/adminClans';
import { AddUser } from './admin/addUser';
import { UserLk } from './user/lk'; import { UserLk } from './user/lk';
import { Clan } from './user/clan'; import { Clan } from './user/clan';
const container = document.getElementById('root')!; const container = document.getElementById('root')!;
const root = createRoot(container); const root = createRoot(container);
const router = createBrowserRouter( const router = createBrowserRouter(
@ -29,6 +34,11 @@ const router = createBrowserRouter(
<Route path='admin/market' element={<Provider store={adminStore}><AdminMarket></AdminMarket></Provider>}></Route> <Route path='admin/market' element={<Provider store={adminStore}><AdminMarket></AdminMarket></Provider>}></Route>
<Route path='admin/market/:id' element={<Provider store={adminStore}><AdminMarketPopUp></AdminMarketPopUp></Provider>}></Route> <Route path='admin/market/:id' element={<Provider store={adminStore}><AdminMarketPopUp></AdminMarketPopUp></Provider>}></Route>
<Route path='admin/market/add' element={<Provider store={adminStore}><AddAdminMarketProduct></AddAdminMarketProduct></Provider>}></Route> <Route path='admin/market/add' element={<Provider store={adminStore}><AddAdminMarketProduct></AddAdminMarketProduct></Provider>}></Route>
<Route path='admin/users' element={<Provider store={adminStore}><UserTable></UserTable></Provider>}></Route>
<Route path='admin/users/:tg' element={<Provider store={adminStore}><AdminUserCard></AdminUserCard></Provider>}></Route>
<Route path='admin/clans/:id' element={<Provider store={adminStore}><AdminClans></AdminClans></Provider>}></Route>
<Route path='admin/users/add' element={<Provider store={adminStore}><AddUser></AddUser></Provider>}></Route>
<Route path="hr"></Route> <Route path="hr"></Route>
<Route path="user/lk/" element={<UserLk />} /> <Route path="user/lk/" element={<UserLk />} />
<Route path='user/clan' element={<Clan />} /> <Route path='user/clan' element={<Clan />} />