add store page
This commit is contained in:
parent
c655868fe0
commit
8ba456c243
|
@ -2,7 +2,7 @@ import { Route, Routes } from 'react-router-dom'
|
|||
import Login from './pages/Login'
|
||||
import Registrations from './pages/Registrations/Registrations'
|
||||
import Repository from './pages/Repository'
|
||||
import Store from './pages/Store'
|
||||
import Store from './pages/Store/Store'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
|
|
@ -5,13 +5,15 @@ type Props = {
|
|||
active: boolean
|
||||
children: React.ReactNode
|
||||
setActive: (param: boolean) => void
|
||||
positionTop?: boolean
|
||||
}
|
||||
|
||||
interface I_Active {
|
||||
interface I_Container {
|
||||
active: boolean
|
||||
positionTop?: boolean
|
||||
}
|
||||
|
||||
const Container = styled.div<I_Active>`
|
||||
const Container = styled.div<I_Container>`
|
||||
position: relative;
|
||||
|
||||
.target {
|
||||
|
@ -21,8 +23,10 @@ const Container = styled.div<I_Active>`
|
|||
.content {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
right: 120%;
|
||||
transform: translateY(-50%);
|
||||
${props => props.positionTop && css`transform: translateY(0%);`}
|
||||
padding: 15px;
|
||||
background: ${(props) => props.theme.bg};
|
||||
${(props) => props.theme.mainShadow}
|
||||
|
@ -42,7 +46,7 @@ const Container = styled.div<I_Active>`
|
|||
}
|
||||
`
|
||||
|
||||
export default function ContextMenu({ active, children, setActive }: Props) {
|
||||
export default function ContextMenu({ active, children, setActive, positionTop }: Props) {
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -59,7 +63,7 @@ export default function ContextMenu({ active, children, setActive }: Props) {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<Container active={active} ref={menuRef}>
|
||||
<Container active={active} ref={menuRef} positionTop={positionTop}>
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ import styled from 'styled-components'
|
|||
|
||||
const InputContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const Input = styled.input`
|
||||
|
@ -13,7 +14,6 @@ const Input = styled.input`
|
|||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import React, { useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const TextareaContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
padding: 15px 20px;
|
||||
border-radius: 12px;
|
||||
background: ${(props) => props.theme.bg};
|
||||
border: 1px solid ${(props) => props.theme.text};
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
resize: none;
|
||||
height: 110px;
|
||||
width: 100%;
|
||||
overflow: -moz-scrollbars-none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
`
|
||||
|
||||
interface PlaceholderProps {
|
||||
showPlaceholder: boolean
|
||||
}
|
||||
|
||||
const Placeholder = styled.label<PlaceholderProps>`
|
||||
background: ${(props) => props.theme.bg};
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
cursor: text;
|
||||
position: absolute;
|
||||
top: ${(props) => (props.showPlaceholder ? '15%' : '-8%')};
|
||||
left: ${(props) => (props.showPlaceholder ? '22px' : '0px')};
|
||||
border-radius: 5px;
|
||||
transform: translateY(-50%);
|
||||
transform: scale(${(props) => (props.showPlaceholder ? '1' : '0.85')});
|
||||
color: #757575;
|
||||
transition: 200ms;
|
||||
padding: ${(props) => (props.showPlaceholder ? '0' : '0 10px')};
|
||||
`
|
||||
|
||||
type Props = {
|
||||
value: string | number
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export default function Textarea({ value, onChange, placeholder }: Props) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const activateTextArea = () => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.focus()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TextareaContainer onClick={activateTextArea}>
|
||||
<textarea value={value} onChange={onChange} ref={textareaRef}></textarea>
|
||||
<Placeholder showPlaceholder={!value}>{placeholder}</Placeholder>
|
||||
</TextareaContainer>
|
||||
)
|
||||
}
|
|
@ -1,16 +1,49 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled, { css } from 'styled-components'
|
||||
import Loader from '../loader/Loader'
|
||||
|
||||
type Props = {
|
||||
loaderState?: boolean
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const TableContainer = styled.table`
|
||||
interface loading {
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const TableContainer = styled.table<loading>`
|
||||
padding: 10px;
|
||||
${(props) => props.theme.mainShadow}
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
|
||||
position: relative;
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${(props) => props.theme.bg};
|
||||
z-index: 1;
|
||||
opacity: 0.7;
|
||||
|
||||
${props => !props.loading && css`display: none;`}
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 30px;
|
||||
background: ${(props) => props.theme.bg};
|
||||
${(props) => props.theme.mainShadow}
|
||||
border-radius: 12px;
|
||||
${props => !props.loading && css`display: none;`}
|
||||
}
|
||||
|
||||
table {
|
||||
background: ${(props) => props.theme.bg};
|
||||
border-collapse: collapse;
|
||||
|
@ -29,9 +62,13 @@ const TableContainer = styled.table`
|
|||
}
|
||||
`
|
||||
|
||||
export default function Table({ children }: Props) {
|
||||
export default function Table({ children, loaderState }: Props) {
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableContainer loading={loaderState}>
|
||||
<div className='loader'></div>
|
||||
<div className='loader-container'>
|
||||
<Loader />
|
||||
</div>
|
||||
<table>{children}</table>
|
||||
</TableContainer>
|
||||
)
|
||||
|
|
|
@ -20,15 +20,6 @@ export default function Layout({ children }: Props) {
|
|||
const currentPath = location.pathname
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const testQuery = async () => {
|
||||
const response = await axios.get(`${HOST_NAME}/packages`)
|
||||
console.log(response.data);
|
||||
}
|
||||
testQuery()
|
||||
}, [])
|
||||
|
||||
// авторизация
|
||||
// useEffect(() => {
|
||||
// const hasCookie = document.cookie.includes('_identid')
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export const HOST_NAME = 'http://localhost:8070/api/v1'
|
||||
export const API_HOST_NAME = 'http://localhost:8070/api/v1'
|
||||
export const HOST_NAME = 'http://localhost:8070'
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 12.5C8.62 12.5 7.5 11.38 7.5 10C7.5 8.62 8.62 7.5 10 7.5C11.38 7.5 12.5 8.62 12.5 10C12.5 11.38 11.38 12.5 10 12.5ZM10 5C7.23875 5 5 7.23875 5 10C5 12.7613 7.23875 15 10 15C12.7613 15 15 12.7613 15 10C15 7.23875 12.7613 5 10 5ZM37.5 21.41L30 13.75L17.5737 26.3888L12.5 21.25L2.5 30.4212V5C2.5 3.62 3.62 2.5 5 2.5H35C36.38 2.5 37.5 3.62 37.5 5V21.41ZM37.5 35C37.5 36.38 36.38 37.5 35 37.5H28.54L19.33 28.1688L30 17.4988L37.5 24.9988V35ZM5 37.5C3.62 37.5 2.5 36.38 2.5 35V33.8262L12.4313 24.9312L25.0013 37.5H5ZM35 0H5C2.23875 0 0 2.23875 0 5V35C0 37.7612 2.23875 40 5 40H35C37.7612 40 40 37.7612 40 35V5C40 2.23875 37.7612 0 35 0Z" fill="#292929"/>
|
||||
</svg>
|
After Width: | Height: | Size: 804 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6668 16.6666L12.5002 12.4999M12.5002 12.4999L8.3335 8.33325M12.5002 12.4999L16.6668 8.33325M12.5002 12.4999L8.3335 16.6666" stroke="#292929" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 323 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="30" height="31" viewBox="0 0 30 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4538 4.29694C11.25 4.69694 11.25 5.22194 11.25 6.27319V14.5232H9.75375C8.65 14.5232 8.0975 14.5232 7.83625 14.7432C7.72415 14.8374 7.63568 14.9566 7.57794 15.0912C7.5202 15.2258 7.49479 15.372 7.50375 15.5182C7.525 15.8607 7.90625 16.2594 8.6675 17.0582L13.915 22.5594C14.2925 22.9569 14.4813 23.1544 14.7025 23.2282C14.8956 23.2929 15.1044 23.2929 15.2975 23.2282C15.5187 23.1544 15.7075 22.9569 16.085 22.5594L21.3325 17.0594C22.095 16.2594 22.475 15.8594 22.495 15.5182C22.5041 15.3721 22.4789 15.2259 22.4214 15.0914C22.3639 14.9568 22.2756 14.8376 22.1638 14.7432C21.9025 14.5232 21.3513 14.5232 20.2463 14.5232H18.75V6.27319C18.75 5.22319 18.75 4.69819 18.545 4.29694C18.3655 3.94402 18.0789 3.65697 17.7262 3.47694C17.3262 3.27319 16.8013 3.27319 15.75 3.27319H14.25C13.2 3.27319 12.675 3.27319 12.2738 3.47694C11.9206 3.65676 11.6336 3.94383 11.4538 4.29694ZM6.25 27.0232C6.25 27.3547 6.3817 27.6727 6.61612 27.9071C6.85054 28.1415 7.16848 28.2732 7.5 28.2732H22.5C22.8315 28.2732 23.1495 28.1415 23.3839 27.9071C23.6183 27.6727 23.75 27.3547 23.75 27.0232C23.75 26.6917 23.6183 26.3737 23.3839 26.1393C23.1495 25.9049 22.8315 25.7732 22.5 25.7732H7.5C7.16848 25.7732 6.85054 25.9049 6.61612 26.1393C6.3817 26.3737 6.25 26.6917 6.25 27.0232Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="26" viewBox="0 0 18 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.45375 1.29694C5.25 1.69694 5.25 2.22194 5.25 3.27319V11.5232H3.75375C2.65 11.5232 2.0975 11.5232 1.83625 11.7432C1.72415 11.8374 1.63568 11.9566 1.57794 12.0912C1.5202 12.2258 1.49479 12.372 1.50375 12.5182C1.525 12.8607 1.90625 13.2594 2.6675 14.0582L7.915 19.5594C8.2925 19.9569 8.48125 20.1544 8.7025 20.2282C8.89556 20.2929 9.10444 20.2929 9.2975 20.2282C9.51875 20.1544 9.7075 19.9569 10.085 19.5594L15.3325 14.0594C16.095 13.2594 16.475 12.8594 16.495 12.5182C16.5041 12.3721 16.4789 12.2259 16.4214 12.0914C16.3639 11.9568 16.2756 11.8376 16.1638 11.7432C15.9025 11.5232 15.3513 11.5232 14.2463 11.5232H12.75V3.27319C12.75 2.22319 12.75 1.69819 12.545 1.29694C12.3655 0.944018 12.0789 0.656972 11.7262 0.476943C11.3262 0.273193 10.8013 0.273193 9.75 0.273193H8.25C7.2 0.273193 6.675 0.273193 6.27375 0.476943C5.92063 0.65676 5.63357 0.943828 5.45375 1.29694ZM0.25 24.0232C0.25 24.3547 0.381696 24.6727 0.616117 24.9071C0.850537 25.1415 1.16848 25.2732 1.5 25.2732H16.5C16.8315 25.2732 17.1495 25.1415 17.3839 24.9071C17.6183 24.6727 17.75 24.3547 17.75 24.0232C17.75 23.6917 17.6183 23.3737 17.3839 23.1393C17.1495 22.9049 16.8315 22.7732 16.5 22.7732H1.5C1.16848 22.7732 0.850537 22.9049 0.616117 23.1393C0.381696 23.3737 0.25 23.6917 0.25 24.0232Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.75567 5.18403C9.87218 5.30058 9.95152 5.44905 9.98366 5.61067C10.0158 5.7723 9.99929 5.93983 9.93624 6.09208C9.87318 6.24433 9.76639 6.37446 9.62939 6.46603C9.49238 6.5576 9.3313 6.6065 9.1665 6.60653H3.33317V14.9399H7.49984C7.72085 14.9399 7.93281 15.0277 8.08909 15.1839C8.24537 15.3402 8.33317 15.5522 8.33317 15.7732C8.33317 15.9942 8.24537 16.2062 8.08909 16.3625C7.93281 16.5187 7.72085 16.6065 7.49984 16.6065H2.49984C2.27882 16.6065 2.06686 16.5187 1.91058 16.3625C1.7543 16.2062 1.6665 15.9942 1.6665 15.7732V5.7732C1.6665 5.55219 1.7543 5.34022 1.91058 5.18394C2.06686 5.02766 2.27882 4.93987 2.49984 4.93987H7.15484L6.07734 3.86237C5.99775 3.78549 5.93426 3.69354 5.89059 3.59187C5.84691 3.4902 5.82392 3.38085 5.82296 3.2702C5.822 3.15955 5.84308 3.04982 5.88499 2.9474C5.92689 2.84499 5.98876 2.75195 6.06701 2.6737C6.14525 2.59546 6.2383 2.53358 6.34071 2.49168C6.44312 2.44978 6.55286 2.4287 6.66351 2.42966C6.77415 2.43062 6.8835 2.45361 6.98517 2.49728C7.08684 2.54096 7.1788 2.60444 7.25567 2.68403L9.75567 5.18403ZM17.4998 4.93987H12.4998C12.2788 4.93987 12.0669 5.02766 11.9106 5.18394C11.7543 5.34022 11.6665 5.55219 11.6665 5.7732C11.6665 5.99421 11.7543 6.20617 11.9106 6.36246C12.0669 6.51874 12.2788 6.60653 12.4998 6.60653H16.6665V14.9399H10.8332C10.6684 14.9399 10.5073 14.9888 10.3703 15.0804C10.2333 15.1719 10.1265 15.3021 10.0634 15.4543C10.0004 15.6066 9.98388 15.7741 10.016 15.9357C10.0482 16.0974 10.1275 16.2458 10.244 16.3624L12.744 18.8624C12.9012 19.0142 13.1117 19.0982 13.3302 19.0963C13.5487 19.0944 13.7577 19.0067 13.9122 18.8522C14.0667 18.6977 14.1543 18.4887 14.1562 18.2702C14.1581 18.0517 14.0741 17.8412 13.9223 17.684L12.8448 16.6065H17.4998C17.7208 16.6065 17.9328 16.5187 18.0891 16.3625C18.2454 16.2062 18.3332 15.9942 18.3332 15.7732V5.7732C18.3332 5.55219 18.2454 5.34022 18.0891 5.18394C17.9328 5.02766 17.7208 4.93987 17.4998 4.93987Z" fill="#292929"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -39,12 +39,17 @@ export default function Registrations({}: Props) {
|
|||
|
||||
const getReg = async () => {
|
||||
setSearchNotification(false)
|
||||
const response = await RegistrationsService.getRegistrations(queryParams)
|
||||
if (response.status === 200) {
|
||||
response.data.Data.length === 0
|
||||
? setSearchNotification(true)
|
||||
: (setRegistrations(response.data.Data),
|
||||
setTotalCount(response.data.Total))
|
||||
setErrorState(false)
|
||||
try {
|
||||
const response = await RegistrationsService.getRegistrations(queryParams)
|
||||
if (response.status === 200) {
|
||||
response.data.Data.length === 0
|
||||
? setSearchNotification(true)
|
||||
: (setRegistrations(response.data.Data),
|
||||
setTotalCount(response.data.Total))
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorState(true)
|
||||
}
|
||||
setLoadingState(false)
|
||||
}
|
||||
|
@ -69,8 +74,10 @@ export default function Registrations({}: Props) {
|
|||
const [notChecked, setNotChecked] = useState<number[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
notChecked.length === registrations.length &&
|
||||
!errorState && (
|
||||
notChecked.length === registrations.length &&
|
||||
(setSelectAll(false), setNotChecked([]))
|
||||
)
|
||||
}, [notChecked])
|
||||
|
||||
const groupDelete = async () => {
|
||||
|
@ -93,11 +100,11 @@ export default function Registrations({}: Props) {
|
|||
|
||||
const groupStop = async () => {
|
||||
const deletePromises = checkedColumns.map((item) =>
|
||||
RegistrationsService.offRegistration(item)
|
||||
)
|
||||
RegistrationsService.offRegistration(item)
|
||||
)
|
||||
|
||||
await Promise.all(deletePromises)
|
||||
getReg()
|
||||
await Promise.all(deletePromises)
|
||||
getReg()
|
||||
}
|
||||
|
||||
const [newReg, setNewReg] = useState<T_NewRegistration>({
|
||||
|
@ -110,10 +117,6 @@ export default function Registrations({}: Props) {
|
|||
|
||||
const [modal, setModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
console.log(registrations)
|
||||
}, [registrations])
|
||||
|
||||
useEffect(() => {
|
||||
setNewReg({
|
||||
RegNum: '',
|
||||
|
@ -133,6 +136,8 @@ export default function Registrations({}: Props) {
|
|||
{loadingState ? (
|
||||
<RegLoaderContainer>
|
||||
<Loader />
|
||||
</RegLoaderContainer>) : errorState ? (<RegLoaderContainer>
|
||||
<h2>Сервер недоступен</h2>
|
||||
</RegLoaderContainer>
|
||||
) : (
|
||||
<RegContainer>
|
||||
|
|
|
@ -18,14 +18,14 @@ const ModalContainer = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
const ErrorMessageContainer = styled.div`
|
||||
const ErrorMessageContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: 20px;
|
||||
`
|
||||
|
||||
const Test = styled.div`
|
||||
const ErrorMessageContainer = styled.div`
|
||||
z-index: 3;
|
||||
`
|
||||
|
||||
|
@ -126,14 +126,14 @@ export default function NewRegModal({
|
|||
</div>
|
||||
</ModalContainer>
|
||||
</Modal>
|
||||
<Test>
|
||||
<ErrorMessageContainer>
|
||||
<Modal modal={errorMessage} setModal={setErrorMessage}>
|
||||
<ErrorMessageContainer>
|
||||
<ErrorMessageContent>
|
||||
<h3>Номер контракта или поставки уже существует</h3>
|
||||
<MainButton onClick={() => setErrorMessage(false)}>Ok</MainButton>
|
||||
</ErrorMessageContainer>
|
||||
</ErrorMessageContent>
|
||||
</Modal>
|
||||
</Test>
|
||||
</ErrorMessageContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ export default function RegTable({
|
|||
<ContextMenu
|
||||
active={contextMenuState}
|
||||
setActive={setContextMenuState}
|
||||
positionTop
|
||||
>
|
||||
<div className='target'>
|
||||
<img
|
||||
|
|
|
@ -33,7 +33,7 @@ const DeleteNotification = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
export default function ({
|
||||
export default function RrgTableItem ({
|
||||
registration,
|
||||
selectAll,
|
||||
notChecked,
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
type Props = {}
|
||||
|
||||
export default function Store({}: Props) {
|
||||
return (
|
||||
<div>Store</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import MainButton from 'src/components/UI/button/MainButton'
|
||||
import SearchInput from 'src/components/UI/input/SearchInput'
|
||||
import Loader from 'src/components/UI/loader/Loader'
|
||||
import Pagination from 'src/components/UI/table/pagination/Pagination'
|
||||
import StoreService from 'src/services/storeServices'
|
||||
import { I_QueryParams } from 'src/services/type'
|
||||
import { T_ColumnsState } from '../Registrations/types'
|
||||
import StoreTable from './blocks/StoreTable'
|
||||
import NewAppModal from './blocks/newAppModal/NewAppModal'
|
||||
import {
|
||||
SearchNotification,
|
||||
StoreContainer,
|
||||
StoreLoaderContainer,
|
||||
StoreNotification,
|
||||
StoreSelectNotification
|
||||
} from './styles'
|
||||
import { T_App } from './type'
|
||||
|
||||
type Props = {}
|
||||
|
||||
export default function Store({}: Props) {
|
||||
const [firstLoadingState, setFirstLoadingState] = useState(true)
|
||||
const [loadingState, setLoadingState] = useState(false)
|
||||
const [errorState, setErrorState] = useState(false)
|
||||
const [searchNotification, setSearchNotification] = useState(false)
|
||||
const [totalCount, setTotalCount] = useState(0)
|
||||
|
||||
const [Apps, setApps] = useState<T_App[]>([])
|
||||
|
||||
const [queryParams, setQueryParams] = useState<I_QueryParams>({
|
||||
page: 1,
|
||||
count: 10,
|
||||
search: '',
|
||||
order: undefined
|
||||
})
|
||||
|
||||
const handleSearchQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQueryParams({ ...queryParams, search: e.target.value })
|
||||
}
|
||||
|
||||
const getApps = async () => {
|
||||
if (!firstLoadingState) setLoadingState(true)
|
||||
setErrorState(false)
|
||||
try {
|
||||
const response = await StoreService.getApp(queryParams)
|
||||
if (response.status === 200) {
|
||||
response.data.Data.length === 0
|
||||
? setSearchNotification(true)
|
||||
: (setApps(response.data.Data),
|
||||
setTotalCount(response.data.Total),
|
||||
setSearchNotification(false))
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorState(true)
|
||||
}
|
||||
|
||||
setLoadingState(false)
|
||||
setFirstLoadingState(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
getApps()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
getApps()
|
||||
}, [queryParams])
|
||||
|
||||
const [activeColumns, setActiveColumns] = useState<T_ColumnsState>({
|
||||
Enabled: { name: 'Состояние', status: true },
|
||||
Name: { name: 'Название', status: true },
|
||||
IconUrl: { name: 'Иконка', status: true },
|
||||
Version: { name: 'Версия', status: true },
|
||||
ShortDescription: { name: 'Краткое описание', status: true },
|
||||
UpdatedDate: { name: 'Дата обновления', status: true },
|
||||
Category: { name: 'Категория', status: false },
|
||||
CreatedDate: { name: 'Дата создания', status: false }
|
||||
})
|
||||
|
||||
const [checkedRows, setCheckedRows] = useState<number[]>([])
|
||||
const [selectAll, setSelectAll] = useState<boolean>(false)
|
||||
const [notChecked, setNotChecked] = useState<number[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
!errorState ||
|
||||
loadingState ||
|
||||
(firstLoadingState &&
|
||||
notChecked.length === Apps.length &&
|
||||
(setSelectAll(false), setNotChecked([])))
|
||||
}, [notChecked])
|
||||
|
||||
const [modal, setModal] = useState(false)
|
||||
|
||||
const groupDelete = async () => {
|
||||
const Promises = checkedRows.map((item) =>
|
||||
StoreService.delete(item)
|
||||
)
|
||||
|
||||
await Promise.all(Promises)
|
||||
getApps()
|
||||
}
|
||||
|
||||
const groupStart = async () => {
|
||||
const Promises = checkedRows.map((item) =>
|
||||
StoreService.interaction('enable', item)
|
||||
)
|
||||
|
||||
await Promise.all(Promises)
|
||||
getApps()
|
||||
}
|
||||
|
||||
const groupStop = async () => {
|
||||
const Promises = checkedRows.map((item) =>
|
||||
StoreService.interaction('disable', item)
|
||||
)
|
||||
|
||||
await Promise.all(Promises)
|
||||
getApps()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{firstLoadingState ? (
|
||||
<StoreLoaderContainer>
|
||||
<Loader />
|
||||
</StoreLoaderContainer>
|
||||
) : errorState ? (
|
||||
<StoreLoaderContainer>
|
||||
<h2>Сервер недоступен</h2>
|
||||
</StoreLoaderContainer>
|
||||
) : (
|
||||
<StoreContainer>
|
||||
{Apps.length === 0 && queryParams.search === '' ? (
|
||||
<StoreNotification>
|
||||
<h2>Список приложений пуст</h2>
|
||||
<MainButton>Добавить приложение</MainButton>
|
||||
</StoreNotification>
|
||||
) : (
|
||||
<>
|
||||
<h2>Система управления и контроля ОСГОС</h2>
|
||||
<SearchInput
|
||||
value={queryParams.search}
|
||||
onChange={handleSearchQueryChange}
|
||||
placeholder='Введите номер контракта, поставки или email'
|
||||
/>
|
||||
<>
|
||||
{searchNotification ? (
|
||||
<SearchNotification>
|
||||
<h3>Ничего не найдено</h3>
|
||||
</SearchNotification>
|
||||
) : (
|
||||
<>
|
||||
{(selectAll || checkedRows.length !== 0) && (
|
||||
<StoreSelectNotification>
|
||||
<h3>
|
||||
{selectAll
|
||||
? `Выбранно: ${totalCount - notChecked.length}`
|
||||
: checkedRows.length !== 0 &&
|
||||
`Выбранно: ${checkedRows.length}`}
|
||||
</h3>
|
||||
<div className='menu'>
|
||||
<img
|
||||
src='/src/images/play.svg'
|
||||
alt=''
|
||||
onClick={groupStart}
|
||||
/>
|
||||
<img
|
||||
src='/src/images/stop.svg'
|
||||
alt=''
|
||||
onClick={groupStop}
|
||||
/>
|
||||
<img
|
||||
src='/src/images/trash.svg'
|
||||
alt=''
|
||||
onClick={groupDelete}
|
||||
/>
|
||||
</div>
|
||||
</StoreSelectNotification>
|
||||
)}
|
||||
<StoreTable
|
||||
loadingState={loadingState}
|
||||
activeColumns={activeColumns}
|
||||
setActiveColumns={setActiveColumns}
|
||||
Apps={Apps}
|
||||
checkedRows={checkedRows}
|
||||
setCheckedRows={setCheckedRows}
|
||||
selectAll={selectAll}
|
||||
notChecked={notChecked}
|
||||
setNotChecked={setNotChecked}
|
||||
setSelectAll={setSelectAll}
|
||||
getApps={getApps}
|
||||
queryParams={queryParams}
|
||||
setQueryParams={setQueryParams}
|
||||
/>
|
||||
<div className='bottom-menu'>
|
||||
<Pagination
|
||||
queryParams={queryParams}
|
||||
setQueryParams={setQueryParams}
|
||||
totalCount={totalCount}
|
||||
/>
|
||||
<MainButton onClick={() => setModal(true)}>
|
||||
Добавить
|
||||
</MainButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</StoreContainer>
|
||||
)}
|
||||
<NewAppModal modal={modal} setModal={setModal} getApps={getApps} />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
import Checkbox from 'src/components/UI/input/Checkbox'
|
||||
import Table from 'src/components/UI/table/Table'
|
||||
import { T_ColumnsState } from 'src/pages/Registrations/types'
|
||||
import { T_App } from '../type'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { I_QueryParams } from 'src/services/type'
|
||||
import { useState } from 'react'
|
||||
import ContextMenu from 'src/components/UI/contextMenu/ContextMenu'
|
||||
import App from 'src/App'
|
||||
import StoreTableItem from './StoreTableItem'
|
||||
|
||||
interface I_TableHeaderItem {
|
||||
active: boolean
|
||||
state: boolean
|
||||
}
|
||||
|
||||
const Item = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const TableHeaderItem = styled.div<I_TableHeaderItem>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
${(props) =>
|
||||
props.state &&
|
||||
css`
|
||||
transform: rotate(180deg);
|
||||
`}
|
||||
${(props) =>
|
||||
props.active
|
||||
? css`
|
||||
display: block;
|
||||
`
|
||||
: css`
|
||||
display: none;
|
||||
`}
|
||||
}
|
||||
`
|
||||
|
||||
type Props = {
|
||||
activeColumns: T_ColumnsState
|
||||
setActiveColumns: (param: any) => void
|
||||
Apps: T_App[]
|
||||
checkedRows: number[]
|
||||
setCheckedRows: (param: any) => void
|
||||
selectAll: boolean
|
||||
notChecked: number[]
|
||||
setNotChecked: (param: any) => void
|
||||
setSelectAll: (param: any) => void
|
||||
getApps: () => void
|
||||
queryParams: I_QueryParams
|
||||
setQueryParams: (param: I_QueryParams) => void
|
||||
loadingState?: boolean
|
||||
}
|
||||
|
||||
export default function StoreTable({
|
||||
activeColumns,
|
||||
setActiveColumns,
|
||||
Apps,
|
||||
checkedRows,
|
||||
setCheckedRows,
|
||||
selectAll,
|
||||
notChecked,
|
||||
setNotChecked,
|
||||
setSelectAll,
|
||||
getApps,
|
||||
queryParams,
|
||||
setQueryParams,
|
||||
loadingState
|
||||
}: Props) {
|
||||
const [contextMenuState, setContextMenuState] = useState(false)
|
||||
|
||||
const [activeSort, setActiveSort] = useState('')
|
||||
const [sortState, setSortState] = useState(false)
|
||||
|
||||
const sort = (value: string) => {
|
||||
const sortOptions: { [key: string]: string } = {
|
||||
'Название': 'name',
|
||||
'Дата обновления': 'updated',
|
||||
'Версия': 'version',
|
||||
// возможен косяк с short
|
||||
'Краткое описание': 'short',
|
||||
'Состояние': 'enabled',
|
||||
};
|
||||
|
||||
const sortParam = sortOptions[value];
|
||||
if (!sortParam) return;
|
||||
|
||||
setActiveSort(value);
|
||||
const newOrder = queryParams.order === sortParam ? `!${sortParam}` : sortParam;
|
||||
setQueryParams({ ...queryParams, order: newOrder });
|
||||
setSortState(queryParams.order === sortParam);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Table loaderState={loadingState}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Checkbox
|
||||
value={
|
||||
selectAll
|
||||
? notChecked.length === 0
|
||||
? 'ok'
|
||||
: 'minus'
|
||||
: checkedRows.length === 0
|
||||
? 'off'
|
||||
: 'minus'
|
||||
}
|
||||
onClick={() => {
|
||||
selectAll
|
||||
? (setSelectAll(false),
|
||||
setNotChecked([]),
|
||||
setCheckedRows([]))
|
||||
: checkedRows.length === 0
|
||||
? (setSelectAll(true), setNotChecked([]))
|
||||
: setCheckedRows([])
|
||||
}}
|
||||
/>
|
||||
</th>
|
||||
{Object.entries(activeColumns).map(
|
||||
([index, item]) =>
|
||||
item.status === true && (
|
||||
<th>
|
||||
<TableHeaderItem
|
||||
key={index}
|
||||
active={activeSort === item.name}
|
||||
state={sortState}
|
||||
onClick={() => sort(item.name)}
|
||||
>
|
||||
<img src='/src/images/arrow-for-table.svg' alt='' />
|
||||
{item.name}
|
||||
</TableHeaderItem>
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
<th>
|
||||
<ContextMenu
|
||||
active={contextMenuState}
|
||||
setActive={setContextMenuState}
|
||||
positionTop
|
||||
>
|
||||
<div className='target'>
|
||||
<img
|
||||
src='/src/images/params.svg'
|
||||
alt=''
|
||||
onClick={() => setContextMenuState(!contextMenuState)}
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{Object.entries(activeColumns).map(([key, item]) => (
|
||||
<Item
|
||||
key={key}
|
||||
onClick={() => {
|
||||
const updatedActiveColumns = { ...activeColumns }
|
||||
updatedActiveColumns[key].status =
|
||||
!updatedActiveColumns[key].status
|
||||
setActiveColumns(updatedActiveColumns)
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
value={item.status ? 'ok' : 'off'}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
<p>{item.name}</p>
|
||||
</Item>
|
||||
))}
|
||||
</div>
|
||||
</ContextMenu>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Apps.map((app) => (
|
||||
<StoreTableItem
|
||||
app={app}
|
||||
selectAll={selectAll}
|
||||
notChecked={notChecked}
|
||||
checkedRows={checkedRows}
|
||||
setNotChecked={setNotChecked}
|
||||
setCheckedRows={setCheckedRows}
|
||||
activeColumns={activeColumns}
|
||||
getApps={getApps}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import MainButton from 'src/components/UI/button/MainButton'
|
||||
import ContextMenu from 'src/components/UI/contextMenu/ContextMenu'
|
||||
import Checkbox from 'src/components/UI/input/Checkbox'
|
||||
import Modal from 'src/components/UI/modal/Modal'
|
||||
import { HOST_NAME } from 'src/constants/host'
|
||||
import { T_ColumnsState } from 'src/pages/Registrations/types'
|
||||
import StoreService from 'src/services/storeServices'
|
||||
import styled from 'styled-components'
|
||||
import { T_App } from '../type'
|
||||
import EditAppModal from './editAppModal/EditAppModal'
|
||||
|
||||
type Props = {
|
||||
app: T_App
|
||||
selectAll: boolean
|
||||
notChecked: number[]
|
||||
checkedRows: number[]
|
||||
setNotChecked: (param: any) => void
|
||||
setCheckedRows: (param: any) => void
|
||||
activeColumns: T_ColumnsState
|
||||
getApps: () => void
|
||||
}
|
||||
|
||||
const Item = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
/* width: 20px; */
|
||||
}
|
||||
`
|
||||
|
||||
const DeleteNotification = styled.div`
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 80px;
|
||||
`
|
||||
|
||||
export default function StoreTableItem({
|
||||
app,
|
||||
selectAll,
|
||||
notChecked,
|
||||
checkedRows,
|
||||
setNotChecked,
|
||||
setCheckedRows,
|
||||
activeColumns,
|
||||
getApps
|
||||
}: Props) {
|
||||
const [contextMenuState, setContextMenuState] = useState(false)
|
||||
const [deleteNotificationState, setDeleteNotificationState] = useState(false)
|
||||
const [editModalState, setEditModalState] = useState(false)
|
||||
|
||||
const interaction = async (type: string) => {
|
||||
setContextMenuState(false)
|
||||
try {
|
||||
await StoreService.interaction(type, app.Id)
|
||||
getApps()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteApp = async () => {
|
||||
setDeleteNotificationState(false)
|
||||
await StoreService.delete(app.Id)
|
||||
getApps()
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={app.Id}>
|
||||
<td>
|
||||
<Checkbox
|
||||
value={
|
||||
selectAll
|
||||
? notChecked.includes(app.Id)
|
||||
? 'off'
|
||||
: 'ok'
|
||||
: checkedRows.includes(app.Id)
|
||||
? 'ok'
|
||||
: 'off'
|
||||
}
|
||||
onClick={() => {
|
||||
selectAll
|
||||
? notChecked.includes(app.Id)
|
||||
? setNotChecked(notChecked.filter((item) => item !== app.Id))
|
||||
: setNotChecked([...notChecked, app.Id])
|
||||
: checkedRows.includes(app.Id)
|
||||
? (setCheckedRows(checkedRows.filter((item) => item !== app.Id)),
|
||||
setNotChecked([...notChecked, app.Id]))
|
||||
: setCheckedRows([...checkedRows, app.Id])
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
{Object.entries(activeColumns).map(([key, item]) =>
|
||||
activeColumns[key as keyof typeof activeColumns].status ? (
|
||||
<td key={key}>
|
||||
<p>
|
||||
{' '}
|
||||
{key === 'Enabled' ? (
|
||||
app[key as keyof typeof app] ? (
|
||||
<img src='/src/images/on.svg' />
|
||||
) : (
|
||||
<img src='/src/images/off.svg' />
|
||||
)
|
||||
) : key === 'IconUrl' ? (
|
||||
app[key as keyof typeof app] === '' ? (
|
||||
<p>Иконки нет</p>
|
||||
) : (
|
||||
<Icon
|
||||
src={`${HOST_NAME}/${app[key as keyof typeof app]}`}
|
||||
alt=''
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
app[key as keyof typeof app]
|
||||
)}
|
||||
</p>
|
||||
</td>
|
||||
) : null
|
||||
)}
|
||||
<td>
|
||||
<ContextMenu active={contextMenuState} setActive={setContextMenuState}>
|
||||
<div className='target'>
|
||||
<img
|
||||
src='/src/images/menu.svg'
|
||||
alt=''
|
||||
onClick={() => setContextMenuState(!contextMenuState)}
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{app.Enabled ? (
|
||||
<Item onClick={() => interaction('disable')}>
|
||||
<img src='/src/images/stop.svg' alt='' />
|
||||
<p>Выключить</p>
|
||||
</Item>
|
||||
) : (
|
||||
<Item onClick={() => interaction('enable')}>
|
||||
<img src='/src/images/play.svg' alt='' />
|
||||
<p>Включить</p>
|
||||
</Item>
|
||||
)}
|
||||
<Item onClick={() => setDeleteNotificationState(true)}>
|
||||
<img src='/src/images/trash.svg' alt='' />
|
||||
<p>Удалить</p>
|
||||
</Item>
|
||||
{app.Url !== '' && (
|
||||
<Link
|
||||
to={`${HOST_NAME}/${app.Url}`}
|
||||
onClick={() => setContextMenuState(false)}
|
||||
>
|
||||
<Item>
|
||||
<img
|
||||
src='/src/images/download.svg'
|
||||
alt=''
|
||||
style={{ width: '20px' }}
|
||||
/>
|
||||
<p>Скачать</p>
|
||||
</Item>
|
||||
</Link>
|
||||
)}
|
||||
<Item onClick={() => interaction('repack')}>
|
||||
<img src='/src/images/reload.svg' alt='' />
|
||||
<p>Перепаковать</p>
|
||||
</Item>
|
||||
<Item onClick={() => (setEditModalState(true), setContextMenuState(false))}>
|
||||
<img src='/src/images/edit.svg' alt='' />
|
||||
<p>Редактировать</p>
|
||||
</Item>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
</td>
|
||||
<Modal
|
||||
modal={deleteNotificationState}
|
||||
setModal={setDeleteNotificationState}
|
||||
>
|
||||
<DeleteNotification>
|
||||
<h3>Вы действительно хотите удалить приложение?</h3>
|
||||
<div className='button-container'>
|
||||
<MainButton
|
||||
color={'secondary'}
|
||||
fullWidth={true}
|
||||
onClick={() => setDeleteNotificationState(false)}
|
||||
>
|
||||
Отмена
|
||||
</MainButton>
|
||||
<MainButton fullWidth={true} onClick={() => deleteApp()}>
|
||||
Удалить
|
||||
</MainButton>
|
||||
</div>
|
||||
</DeleteNotification>
|
||||
</Modal>
|
||||
<EditAppModal
|
||||
modal={editModalState}
|
||||
setModal={setEditModalState}
|
||||
app={app}
|
||||
getApps={getApps}
|
||||
/>
|
||||
</tr>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,395 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import MainButton from 'src/components/UI/button/MainButton'
|
||||
import MainInput from 'src/components/UI/input/MainInput'
|
||||
import Textarea from 'src/components/UI/input/Textarea'
|
||||
import Loader from 'src/components/UI/loader/Loader'
|
||||
import Modal from 'src/components/UI/modal/Modal'
|
||||
import { HOST_NAME } from 'src/constants/host'
|
||||
import StoreService from 'src/services/storeServices'
|
||||
import { T_App, T_NewApp } from '../../type'
|
||||
import {
|
||||
LoaderContainer,
|
||||
NewAppButtonBlock,
|
||||
NewAppErrorMessage,
|
||||
NewAppErrorMessageContainer,
|
||||
NewAppFileBlock,
|
||||
NewAppIconBlock,
|
||||
NewAppModalContainer,
|
||||
NewAppScreenshotsBlock
|
||||
} from '../newAppModal/styles'
|
||||
|
||||
type Props = {
|
||||
modal: boolean
|
||||
setModal: (param: boolean) => void
|
||||
app: T_App
|
||||
getApps: () => void
|
||||
}
|
||||
|
||||
interface screenshotArr {
|
||||
id: number
|
||||
file: File | string
|
||||
}
|
||||
|
||||
export default function EditAppModal({ modal, setModal, app, getApps }: Props) {
|
||||
const [error, setError] = useState({
|
||||
state: false,
|
||||
message: ''
|
||||
})
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const setErrorState = (value: boolean) => {
|
||||
setError({ ...error, state: value })
|
||||
}
|
||||
|
||||
const [editApp, setEditApp] = useState<T_NewApp>({
|
||||
Name: app.Name,
|
||||
Description: app.Description,
|
||||
ShortDescription: app.ShortDescription,
|
||||
Version: app.Version,
|
||||
Category: app.Category
|
||||
})
|
||||
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEditApp({ ...editApp, Name: e.target.value })
|
||||
}
|
||||
const handleDescriptionChange = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>
|
||||
) => {
|
||||
setEditApp({ ...editApp, Description: e.target.value })
|
||||
}
|
||||
const handleShortDescriptionChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setEditApp({ ...editApp, ShortDescription: e.target.value })
|
||||
}
|
||||
const handleVersionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEditApp({ ...editApp, Version: e.target.value })
|
||||
}
|
||||
const handleCategoryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEditApp({ ...editApp, Category: e.target.value })
|
||||
}
|
||||
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setFormValid(
|
||||
editApp.Name !== '' &&
|
||||
editApp.Description !== '' &&
|
||||
editApp.ShortDescription !== '' &&
|
||||
editApp.Version !== '' &&
|
||||
editApp.Category !== ''
|
||||
)
|
||||
}, [editApp])
|
||||
|
||||
const iconInputRef = useRef<HTMLInputElement>(null)
|
||||
const screenshotsInputRef = useRef<HTMLInputElement>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const [selectedImage, setSelectedImage] = useState<File | string | null>(null)
|
||||
const [selectedFile, setSelectedFile] = useState<File | string | null>(null)
|
||||
|
||||
const [selectedScreenshots, setSelectedScreenshots] = useState<
|
||||
screenshotArr[]
|
||||
>([])
|
||||
const [deleteScreenshots, setDeleteScreenshots] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedScreenshots(
|
||||
app.ImageUrl.map((item, index) => ({
|
||||
id: new Date().getTime() + index,
|
||||
file: item
|
||||
}))
|
||||
)
|
||||
setSelectedImage(app.IconUrl)
|
||||
setSelectedFile(app.Url)
|
||||
}, [])
|
||||
|
||||
// Иконка
|
||||
const handleImageChange = () => {
|
||||
const file = iconInputRef.current?.files?.[0]
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
setSelectedImage(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageClick = () => {
|
||||
iconInputRef.current?.click()
|
||||
}
|
||||
|
||||
// Скриншоты
|
||||
const handleScreenshotsChange = () => {
|
||||
if (screenshotsInputRef.current?.files) {
|
||||
const filesArray = Array.from(screenshotsInputRef.current.files)
|
||||
const newScreenshots = filesArray.map((file, index) => ({
|
||||
id: new Date().getTime() + index,
|
||||
file: file
|
||||
}))
|
||||
setSelectedScreenshots((prevScreenshots) => [
|
||||
...prevScreenshots,
|
||||
...newScreenshots
|
||||
])
|
||||
}
|
||||
if (screenshotsInputRef.current) {
|
||||
screenshotsInputRef.current.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const deleteScreenshot = (id: number) => {
|
||||
const screenshotToDelete: screenshotArr | undefined =
|
||||
selectedScreenshots.find((screenshot) => screenshot.id === id)
|
||||
if (screenshotToDelete) {
|
||||
if (typeof screenshotToDelete.file === 'string') {
|
||||
setDeleteScreenshots((prevState) => [
|
||||
...prevState,
|
||||
screenshotToDelete.file as string
|
||||
])
|
||||
}
|
||||
setSelectedScreenshots((prevScreenshots) =>
|
||||
prevScreenshots.filter((screenshot) => screenshot.id !== id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleScreenshotsClick = () => {
|
||||
screenshotsInputRef.current?.click()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(selectedScreenshots)
|
||||
console.log(deleteScreenshots)
|
||||
}, [selectedScreenshots])
|
||||
|
||||
// Файл
|
||||
const handleFileChange = () => {
|
||||
const file = fileInputRef.current?.files?.[0]
|
||||
if (file) {
|
||||
setSelectedFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileClick = () => {
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
const updateApp = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
// Данные
|
||||
if (
|
||||
!(
|
||||
editApp.Category === app.Category &&
|
||||
editApp.Description === app.Description &&
|
||||
editApp.Name === app.Name &&
|
||||
editApp.ShortDescription === app.ShortDescription &&
|
||||
editApp.Version === app.Version
|
||||
)
|
||||
) {
|
||||
const response = await StoreService.editData(app.Id, editApp)
|
||||
if (response.status === 406) {
|
||||
setError({ state: true, message: response.data })
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Иконка
|
||||
if (app.IconUrl !== selectedImage) {
|
||||
const response =
|
||||
selectedImage instanceof File &&
|
||||
(await StoreService.addIcon(selectedImage, app.Id))
|
||||
if (response && response.status === 406) {
|
||||
setError({ state: true, message: response.data })
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Скриншоты
|
||||
if (
|
||||
!(
|
||||
app.ImageUrl.length === selectedScreenshots.length &&
|
||||
deleteScreenshots.length === 0
|
||||
)
|
||||
) {
|
||||
await Promise.all(
|
||||
deleteScreenshots.map(
|
||||
async (item) => await StoreService.deleteScreenshots(app.Id, item)
|
||||
)
|
||||
)
|
||||
const filesToAdd: File[] = selectedScreenshots
|
||||
.filter((item) => item.file instanceof File)
|
||||
.map((item) => item.file as File)
|
||||
|
||||
filesToAdd.length !== 0 &&
|
||||
(await StoreService.addScreenshots(filesToAdd, app.Id))
|
||||
}
|
||||
// Файл
|
||||
if (app.Url !== selectedFile && selectedFile instanceof File) {
|
||||
const response = await StoreService.addFile(selectedFile, app.Id)
|
||||
if (response.status === 406) {
|
||||
setError({ state: true, message: response.data })
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
setModal(false)
|
||||
getApps()
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal modal={modal} setModal={setModal}>
|
||||
<NewAppModalContainer>
|
||||
{loading && (
|
||||
<LoaderContainer>
|
||||
<div className='container'></div>
|
||||
<Loader />
|
||||
</LoaderContainer>
|
||||
)}
|
||||
<h2>Программа для магазина</h2>
|
||||
<div className='top-block'>
|
||||
<div className='input-block'>
|
||||
<div className='top-input-block'>
|
||||
<MainInput
|
||||
placeholder='Название программы'
|
||||
value={editApp.Name}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<MainInput
|
||||
placeholder='Версия программы'
|
||||
value={editApp.Version}
|
||||
onChange={handleVersionChange}
|
||||
/>
|
||||
</div>
|
||||
<MainInput
|
||||
placeholder='Краткое описание'
|
||||
value={editApp.ShortDescription}
|
||||
onChange={handleShortDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NewAppIconBlock>
|
||||
{selectedImage ? (
|
||||
<img
|
||||
src={
|
||||
typeof selectedImage === 'string'
|
||||
? `${HOST_NAME}/${selectedImage}`
|
||||
: URL.createObjectURL(selectedImage)
|
||||
}
|
||||
alt='Selected'
|
||||
className='icon'
|
||||
onClick={handleImageClick}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={handleImageClick} className='file-input'>
|
||||
<img src='/src/images/Icon.svg' alt='' />
|
||||
<p>
|
||||
Выбрать
|
||||
<br />
|
||||
иконку
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
ref={iconInputRef}
|
||||
type='file'
|
||||
accept='image/*'
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
</NewAppIconBlock>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
placeholder='Полное описание'
|
||||
value={editApp.Description}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
<MainInput
|
||||
placeholder='Категория'
|
||||
value={editApp.Category}
|
||||
onChange={handleCategoryChange}
|
||||
/>
|
||||
<NewAppScreenshotsBlock>
|
||||
<div className='screenshots-input' onClick={handleScreenshotsClick}>
|
||||
<img src='/src/images/Icon.svg' alt='' />
|
||||
<p>
|
||||
Выбрать
|
||||
<br />
|
||||
скриншот
|
||||
</p>
|
||||
<input
|
||||
ref={screenshotsInputRef}
|
||||
type='file'
|
||||
accept='image/*'
|
||||
multiple
|
||||
onChange={handleScreenshotsChange}
|
||||
/>
|
||||
</div>
|
||||
{selectedScreenshots &&
|
||||
selectedScreenshots.map((screenshot) => (
|
||||
<div className='screenshots-item'>
|
||||
<div
|
||||
className='close-icon'
|
||||
onClick={() => deleteScreenshot(screenshot.id)}
|
||||
>
|
||||
<img src='/src/images/close.svg' alt='' />
|
||||
</div>
|
||||
<img
|
||||
key={screenshot.id}
|
||||
src={
|
||||
typeof screenshot.file === 'string'
|
||||
? `${HOST_NAME}/${screenshot.file}`
|
||||
: URL.createObjectURL(screenshot.file)
|
||||
}
|
||||
alt={`Screenshot ${screenshot.id}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</NewAppScreenshotsBlock>
|
||||
<NewAppFileBlock>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='.exe'
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
{selectedFile ? (
|
||||
<p>
|
||||
<span>Выбранный файл: </span>
|
||||
{typeof selectedFile === 'string'
|
||||
? selectedFile.replace('packages/', '')
|
||||
: selectedFile.name}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<span>Файл не выбран</span>
|
||||
</p>
|
||||
)}
|
||||
<MainButton onClick={handleFileClick}>Выбрать файл</MainButton>
|
||||
</NewAppFileBlock>
|
||||
<NewAppButtonBlock>
|
||||
<MainButton
|
||||
color='secondary'
|
||||
fullWidth
|
||||
onClick={() => setModal(false)}
|
||||
>
|
||||
Отмена
|
||||
</MainButton>
|
||||
<MainButton fullWidth onClick={updateApp} disabled={!formValid}>
|
||||
Сохранить
|
||||
</MainButton>
|
||||
</NewAppButtonBlock>
|
||||
</NewAppModalContainer>
|
||||
</Modal>
|
||||
<NewAppErrorMessageContainer>
|
||||
<Modal modal={error.state} setModal={setErrorState}>
|
||||
<NewAppErrorMessage>
|
||||
<h3>{error.message}</h3>
|
||||
<MainButton onClick={() => setErrorState(false)}>Ok</MainButton>
|
||||
</NewAppErrorMessage>
|
||||
</Modal>
|
||||
</NewAppErrorMessageContainer>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import MainButton from 'src/components/UI/button/MainButton'
|
||||
import MainInput from 'src/components/UI/input/MainInput'
|
||||
import Textarea from 'src/components/UI/input/Textarea'
|
||||
import Modal from 'src/components/UI/modal/Modal'
|
||||
import StoreService from 'src/services/storeServices'
|
||||
import { T_NewApp } from '../../type'
|
||||
import {
|
||||
NewAppButtonBlock,
|
||||
NewAppErrorMessage,
|
||||
NewAppErrorMessageContainer,
|
||||
NewAppFileBlock,
|
||||
NewAppIconBlock,
|
||||
NewAppModalContainer,
|
||||
NewAppScreenshotsBlock
|
||||
} from './styles'
|
||||
|
||||
interface screenshotArr {
|
||||
id: number
|
||||
file: File
|
||||
}
|
||||
|
||||
type Props = {
|
||||
modal: boolean
|
||||
setModal: (param: boolean) => void
|
||||
getApps: () => void
|
||||
}
|
||||
|
||||
export default function NewAppModal({ modal, setModal, getApps }: Props) {
|
||||
const [newApp, setNewApp] = useState<T_NewApp>({
|
||||
Name: '',
|
||||
Description: '',
|
||||
ShortDescription: '',
|
||||
Version: '',
|
||||
Category: ''
|
||||
})
|
||||
const [selectedImage, setSelectedImage] = useState<File | null>(null)
|
||||
const [selectedScreenshots, setSelectedScreenshots] = useState<
|
||||
screenshotArr[]
|
||||
>([])
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setNewApp({
|
||||
Name: '',
|
||||
Description: '',
|
||||
ShortDescription: '',
|
||||
Version: '',
|
||||
Category: ''
|
||||
})
|
||||
setSelectedImage(null)
|
||||
setSelectedScreenshots([])
|
||||
setSelectedFile(null)
|
||||
}, [modal])
|
||||
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setFormValid(
|
||||
newApp.Name !== '' &&
|
||||
newApp.Description !== '' &&
|
||||
newApp.ShortDescription !== '' &&
|
||||
newApp.Version !== '' &&
|
||||
newApp.Category !== ''
|
||||
)
|
||||
}, [newApp])
|
||||
|
||||
const [errorState, setErrorState] = useState(false)
|
||||
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewApp({ ...newApp, Name: e.target.value })
|
||||
}
|
||||
const handleDescriptionChange = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>
|
||||
) => {
|
||||
setNewApp({ ...newApp, Description: e.target.value })
|
||||
}
|
||||
const handleShortDescriptionChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setNewApp({ ...newApp, ShortDescription: e.target.value })
|
||||
}
|
||||
const handleVersionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewApp({ ...newApp, Version: e.target.value })
|
||||
}
|
||||
const handleCategoryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewApp({ ...newApp, Category: e.target.value })
|
||||
}
|
||||
|
||||
const iconInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleImageChange = () => {
|
||||
const file = iconInputRef.current?.files?.[0]
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
setSelectedImage(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageClick = () => {
|
||||
iconInputRef.current?.click()
|
||||
}
|
||||
|
||||
const screenshotsInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleScreenshotsChange = () => {
|
||||
if (screenshotsInputRef.current?.files) {
|
||||
const filesArray = Array.from(screenshotsInputRef.current.files)
|
||||
const newScreenshots = filesArray.map((file, index) => ({
|
||||
id: new Date().getTime() + index,
|
||||
file: file
|
||||
}))
|
||||
setSelectedScreenshots((prevScreenshots) => [
|
||||
...prevScreenshots,
|
||||
...newScreenshots
|
||||
])
|
||||
}
|
||||
if (screenshotsInputRef.current) {
|
||||
screenshotsInputRef.current.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const deleteScreenshot = (id: number) => {
|
||||
setSelectedScreenshots((prevScreenshots) =>
|
||||
prevScreenshots.filter((screenshot) => screenshot.id !== id)
|
||||
)
|
||||
}
|
||||
|
||||
const handleScreenshotsClick = () => {
|
||||
screenshotsInputRef.current?.click()
|
||||
}
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleFileChange = () => {
|
||||
const file = fileInputRef.current?.files?.[0]
|
||||
if (file) {
|
||||
setSelectedFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileClick = () => {
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
const appCreate = async () => {
|
||||
try {
|
||||
const response = await StoreService.createApp(newApp)
|
||||
if (response.status === 200) {
|
||||
selectedScreenshots !== null &&
|
||||
(await StoreService.addScreenshots(
|
||||
selectedScreenshots.map((item) => item.file),
|
||||
response.data.Id
|
||||
))
|
||||
selectedImage !== null &&
|
||||
(await StoreService.addIcon(selectedImage, response.data.Id))
|
||||
selectedFile !== null &&
|
||||
(await StoreService.addFile(selectedFile, response.data.Id))
|
||||
} else if (response.status === 406) {
|
||||
setErrorState(true)
|
||||
}
|
||||
setModal(false)
|
||||
getApps()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal modal={modal} setModal={setModal}>
|
||||
<NewAppModalContainer>
|
||||
<h2>Программа для магазина</h2>
|
||||
<div className='top-block'>
|
||||
<div className='input-block'>
|
||||
<div className='top-input-block'>
|
||||
<MainInput
|
||||
placeholder='Название программы'
|
||||
value={newApp.Name}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<MainInput
|
||||
placeholder='Версия программы'
|
||||
value={newApp.Version}
|
||||
onChange={handleVersionChange}
|
||||
/>
|
||||
</div>
|
||||
<MainInput
|
||||
placeholder='Краткое описание'
|
||||
value={newApp.ShortDescription}
|
||||
onChange={handleShortDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NewAppIconBlock>
|
||||
{selectedImage ? (
|
||||
<img
|
||||
src={URL.createObjectURL(selectedImage)}
|
||||
alt='Selected'
|
||||
className='icon'
|
||||
onClick={handleImageClick}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={handleImageClick} className='file-input'>
|
||||
<img src='/src/images/Icon.svg' alt='' />
|
||||
<p>
|
||||
Выбрать
|
||||
<br />
|
||||
иконку
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
ref={iconInputRef}
|
||||
type='file'
|
||||
accept='image/*'
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
</NewAppIconBlock>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
placeholder='Полное описание'
|
||||
value={newApp.Description}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
<MainInput
|
||||
placeholder='Категория'
|
||||
value={newApp.Category}
|
||||
onChange={handleCategoryChange}
|
||||
/>
|
||||
<NewAppScreenshotsBlock>
|
||||
<div className='screenshots-input' onClick={handleScreenshotsClick}>
|
||||
<img src='/src/images/Icon.svg' alt='' />
|
||||
<p>
|
||||
Выбрать
|
||||
<br />
|
||||
скриншот
|
||||
</p>
|
||||
<input
|
||||
ref={screenshotsInputRef}
|
||||
type='file'
|
||||
accept='image/*'
|
||||
multiple
|
||||
onChange={handleScreenshotsChange}
|
||||
/>
|
||||
</div>
|
||||
{selectedScreenshots &&
|
||||
selectedScreenshots.map((screenshot) => (
|
||||
<div className='screenshots-item'>
|
||||
<div
|
||||
className='close-icon'
|
||||
onClick={() => deleteScreenshot(screenshot.id)}
|
||||
>
|
||||
<img src='/src/images/close.svg' alt='' />
|
||||
</div>
|
||||
<img
|
||||
key={screenshot.id}
|
||||
src={URL.createObjectURL(screenshot.file)}
|
||||
alt={`Screenshot ${screenshot.id}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</NewAppScreenshotsBlock>
|
||||
<NewAppFileBlock>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='.exe'
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
{selectedFile ? (
|
||||
<p>
|
||||
<span>Выбранный файл: </span>
|
||||
{selectedFile.name}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<span>Файл не выбран</span>
|
||||
</p>
|
||||
)}
|
||||
<MainButton onClick={handleFileClick}>Выбрать файл</MainButton>
|
||||
</NewAppFileBlock>
|
||||
<NewAppButtonBlock>
|
||||
<MainButton
|
||||
color='secondary'
|
||||
fullWidth
|
||||
onClick={() => setModal(false)}
|
||||
>
|
||||
Отмена
|
||||
</MainButton>
|
||||
<MainButton fullWidth onClick={appCreate} disabled={!formValid}>
|
||||
Создать
|
||||
</MainButton>
|
||||
</NewAppButtonBlock>
|
||||
</NewAppModalContainer>
|
||||
</Modal>
|
||||
<NewAppErrorMessageContainer>
|
||||
<Modal modal={errorState} setModal={setErrorState}>
|
||||
<NewAppErrorMessage>
|
||||
<h3>Имя и версия уже существует</h3>
|
||||
<MainButton onClick={() => setErrorState(false)}>Ok</MainButton>
|
||||
</NewAppErrorMessage>
|
||||
</Modal>
|
||||
</NewAppErrorMessageContainer>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
export const NewAppModalContainer = styled.div`
|
||||
position: relative;
|
||||
width: 826px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
.top-block {
|
||||
display: flex;
|
||||
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.input-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.top-input-block {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
export const NewAppIconBlock = styled.div`
|
||||
cursor: pointer;
|
||||
height: 125px;
|
||||
width: 125px;
|
||||
|
||||
.icon {
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed ${(props) => props.theme.textColor};
|
||||
height: 125px;
|
||||
width: 125px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
`
|
||||
|
||||
export const NewAppScreenshotsBlock = styled.div`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
width: 826px;
|
||||
padding-bottom: 10px;
|
||||
overflow-x: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 5px; /* Ширина скролбара */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #e3e3e3; /* Цвет фона трека скролбара */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #b0b0b0; /* Цвет ползунка */
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.screenshots-input {
|
||||
gap: 10px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed ${(props) => props.theme.textColor};
|
||||
height: 130px;
|
||||
min-width: 230px;
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.screenshots-item {
|
||||
${(props) => props.theme.mainShadow}
|
||||
position: relative;
|
||||
height: 130px;
|
||||
min-width: 230px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
height: 130px;
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.close-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
background: ${(props) => props.theme.bg};
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
|
||||
img {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const NewAppFileBlock = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
}
|
||||
`
|
||||
|
||||
export const NewAppButtonBlock = styled.div`
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
`
|
||||
|
||||
export const NewAppErrorMessageContainer = styled.div`
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
`
|
||||
|
||||
export const NewAppErrorMessage = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: 20px;
|
||||
`
|
||||
|
||||
export const LoaderContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
background: ${(props) => props.theme.bg};
|
||||
width: 101%;
|
||||
height: 101%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,68 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
export const StoreLoaderContainer = styled.div`
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
`
|
||||
|
||||
export const StoreContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
padding: 40px 20px;
|
||||
|
||||
.bottom-menu {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`
|
||||
|
||||
export const StoreNotification = styled.div`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export const SearchNotification = styled.div`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
`
|
||||
|
||||
export const StoreSelectNotification = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
${(props) => props.theme.mainShadow}
|
||||
border-radius: 12px;
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: 300ms;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.focusColor};
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,25 @@
|
|||
export type T_NewApp = {
|
||||
Name: string
|
||||
Description: string
|
||||
ShortDescription: string
|
||||
Version: string
|
||||
Category: string
|
||||
}
|
||||
|
||||
export interface T_App {
|
||||
Id: number;
|
||||
Name: string;
|
||||
Description: string;
|
||||
ShortDescription: string;
|
||||
IconUrl: string;
|
||||
ImageUrl: string[];
|
||||
Version: string;
|
||||
DependenciesReceived: boolean;
|
||||
Arch: "amd64" | "x86" | string;
|
||||
Category: string;
|
||||
CreatedDate: string;
|
||||
UpdatedDate: string;
|
||||
Repo: boolean;
|
||||
Url: string;
|
||||
Enabled: boolean;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import axios, { AxiosResponse } from 'axios'
|
||||
import { HOST_NAME } from 'src/constants/host'
|
||||
import { API_HOST_NAME } from 'src/constants/host'
|
||||
import { T_NewRegistration } from 'src/pages/Registrations/types'
|
||||
import { I_QueryParams } from './type'
|
||||
|
||||
|
@ -8,7 +8,7 @@ export default class RegistrationsService {
|
|||
queryParams: I_QueryParams
|
||||
): Promise<AxiosResponse> {
|
||||
const { page, count, search, order } = queryParams
|
||||
const url = `${HOST_NAME}/regs`
|
||||
const url = `${API_HOST_NAME}/regs`
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (page !== undefined) params.append('page', String(page))
|
||||
|
@ -35,7 +35,7 @@ export default class RegistrationsService {
|
|||
newReg.Count = parseInt(newReg.Count, 10)
|
||||
}
|
||||
try {
|
||||
const response = await axios.post(`${HOST_NAME}/regs`, newReg)
|
||||
const response = await axios.post(`${API_HOST_NAME}/regs`, newReg)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
|
@ -44,7 +44,7 @@ export default class RegistrationsService {
|
|||
|
||||
static async deleteRegistration(id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.delete(`${HOST_NAME}/regs/${id}`)
|
||||
const response = await axios.delete(`${API_HOST_NAME}/regs/${id}`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
|
@ -53,7 +53,7 @@ export default class RegistrationsService {
|
|||
|
||||
static async onRegistration(id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.post(`${HOST_NAME}/regs/${id}/state/enable`)
|
||||
const response = await axios.post(`${API_HOST_NAME}/regs/${id}/state/enable`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
|
@ -62,7 +62,7 @@ export default class RegistrationsService {
|
|||
|
||||
static async offRegistration(id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.post(`${HOST_NAME}/regs/${id}/state/disable`)
|
||||
const response = await axios.post(`${API_HOST_NAME}/regs/${id}/state/disable`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
|
@ -80,7 +80,7 @@ export default class RegistrationsService {
|
|||
reg.Count = parseInt(reg.Count, 10)
|
||||
}
|
||||
try {
|
||||
const response = await axios.post(`${HOST_NAME}/regs/${id}`, reg)
|
||||
const response = await axios.post(`${API_HOST_NAME}/regs/${id}`, reg)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
import axios, { AxiosResponse } from 'axios'
|
||||
import { API_HOST_NAME } from 'src/constants/host'
|
||||
import { T_NewApp } from 'src/pages/Store/type'
|
||||
import { I_QueryParams } from './type'
|
||||
|
||||
export default class StoreService {
|
||||
static async getApp(
|
||||
queryParams: I_QueryParams
|
||||
): Promise<AxiosResponse> {
|
||||
const { page, count, search, order } = queryParams
|
||||
const url = `${API_HOST_NAME}/packages?full=true`
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (page !== undefined) params.append('page', String(page))
|
||||
if (count !== undefined) params.append('count', String(count))
|
||||
if (search !== undefined && search !== '') params.append('search', search)
|
||||
if (order !== undefined) params.append('order', order)
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, { params })
|
||||
console.log(response.data)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async createApp(newApp: T_NewApp): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.post(`${API_HOST_NAME}/packages`, newApp)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async addScreenshots(
|
||||
screenshots: File[],
|
||||
id: number
|
||||
): Promise<AxiosResponse> {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
screenshots.forEach((screenshot) => {
|
||||
formData.append(`files`, screenshot)
|
||||
})
|
||||
|
||||
const response = await axios.post(
|
||||
`${API_HOST_NAME}/packages/${id}/images/upload`,
|
||||
formData
|
||||
)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async addIcon(icon: File, id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append(`file`, icon)
|
||||
const response = await axios.post(
|
||||
`${API_HOST_NAME}/packages/${id}/icon/upload`,
|
||||
formData
|
||||
)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async addFile(file: File, id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append(`file`, file)
|
||||
const response = await axios.post(
|
||||
`${API_HOST_NAME}/packages/${id}/package/upload`,
|
||||
formData
|
||||
)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async interaction(type: string, id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.post(`${API_HOST_NAME}/packages/${id}/${type}`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteScreenshots(id: number, name: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.delete(`${API_HOST_NAME}/packages/${id}/image/${name.replace('images/', '')}`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async editData(id: number, editApp: T_NewApp): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.post(`${API_HOST_NAME}/packages/${id}`, editApp)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
|
||||
static async delete(id: number): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.delete(`${API_HOST_NAME}/packages/${id}`)
|
||||
return response
|
||||
} catch (error: any) {
|
||||
return error.response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import axios, { AxiosResponse } from 'axios'
|
||||
import { HOST_NAME } from 'src/constants/host'
|
||||
import { API_HOST_NAME } from 'src/constants/host'
|
||||
|
||||
//
|
||||
|
||||
|
@ -7,7 +7,7 @@ export default class UserService {
|
|||
static async login(name: string, password: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${HOST_NAME}/auth?a123=${name}&a321=${password}`
|
||||
`${API_HOST_NAME}/auth?a123=${name}&a321=${password}`
|
||||
);
|
||||
return response
|
||||
} catch (error: any) {
|
||||
|
|
Loading…
Reference in New Issue