From aff278622c137a74dc2ec49ef00c38618b3aa9c7 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Fri, 1 May 2020 08:25:02 +0800 Subject: [PATCH] add table view mode for players management --- package.json | 4 +- .../src/scripts/hooks/useIsLargeScreen.ts | 13 +++ .../src/styles/table-mode-switch.module.scss | 19 +++ .../src/views/admin/PlayersManagement/Row.tsx | 81 +++++++++++++ .../views/admin/PlayersManagement/index.tsx | 108 ++++++++++++++---- .../views/admin/PlayersManagement.test.tsx | 71 ++++++++++++ 6 files changed, 274 insertions(+), 22 deletions(-) create mode 100644 resources/assets/src/scripts/hooks/useIsLargeScreen.ts create mode 100644 resources/assets/src/styles/table-mode-switch.module.scss create mode 100644 resources/assets/src/views/admin/PlayersManagement/Row.tsx diff --git a/package.json b/package.json index 3d2c0248..ad32fbaa 100644 --- a/package.json +++ b/package.json @@ -219,9 +219,9 @@ "node" ], "moduleNameMapper": { - "^@/(.*)$": "/resources/assets/src/$1", "\\.(s?css|styl)$": "/resources/assets/tests/__mocks__/style.ts", - "\\.(png|jpg)$": "/resources/assets/tests/__mocks__/file.ts" + "\\.(png|jpg)$": "/resources/assets/tests/__mocks__/file.ts", + "^@/(.*)$": "/resources/assets/src/$1" }, "setupFilesAfterEnv": [ "/resources/assets/tests/setup.ts" diff --git a/resources/assets/src/scripts/hooks/useIsLargeScreen.ts b/resources/assets/src/scripts/hooks/useIsLargeScreen.ts new file mode 100644 index 00000000..b7019916 --- /dev/null +++ b/resources/assets/src/scripts/hooks/useIsLargeScreen.ts @@ -0,0 +1,13 @@ +import { useState, useEffect } from 'react' + +export default function useIsLargeScreen() { + const [isLarge, setIsLarge] = useState(false) + + useEffect(() => { + if (window.innerWidth >= 992) { + setIsLarge(true) + } + }, []) + + return isLarge +} diff --git a/resources/assets/src/styles/table-mode-switch.module.scss b/resources/assets/src/styles/table-mode-switch.module.scss new file mode 100644 index 00000000..79794a46 --- /dev/null +++ b/resources/assets/src/styles/table-mode-switch.module.scss @@ -0,0 +1,19 @@ +@use './breakpoints'; + +.header { + display: flex; + & > div { + margin-left: 4px; + + & label { + cursor: pointer; + } + } + + @include breakpoints.less-than('sm') { + flex-wrap: wrap; + & > div { + margin: 7px 0 0 0; + } + } +} diff --git a/resources/assets/src/views/admin/PlayersManagement/Row.tsx b/resources/assets/src/views/admin/PlayersManagement/Row.tsx new file mode 100644 index 00000000..e2ac1662 --- /dev/null +++ b/resources/assets/src/views/admin/PlayersManagement/Row.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import { t } from '@/scripts/i18n' +import { Player } from '@/scripts/types' +import ButtonEdit from '@/components/ButtonEdit' + +interface Props { + player: Player + onUpdateName(): void + onUpdateOwner(): void + onUpdateTexture(): void + onDelete(): void +} + +const Row: React.FC = (props) => { + const { player } = props + + return ( + + {player.pid} + + {player.name} + + + + + + {player.uid} + + + + + + {player.tid_skin > 0 && ( + + {`${player.name} + + )} + {player.tid_cape > 0 && ( + + {`${player.name} + + )} + + {player.last_modified} + + + + + + ) +} + +export default Row diff --git a/resources/assets/src/views/admin/PlayersManagement/index.tsx b/resources/assets/src/views/admin/PlayersManagement/index.tsx index 89c1a6a9..963e1b6b 100644 --- a/resources/assets/src/views/admin/PlayersManagement/index.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/index.tsx @@ -1,13 +1,16 @@ import React, { useState, useEffect } from 'react' import { hot } from 'react-hot-loader/root' import { useImmer } from 'use-immer' +import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen' import { t } from '@/scripts/i18n' import * as fetch from '@/scripts/net' import { Player, Paginator } from '@/scripts/types' import { toast, showModal } from '@/scripts/notify' +import modeSwitchStyles from '@/styles/table-mode-switch.module.scss' import Loading from '@/components/Loading' import Pagination from '@/components/Pagination' import Card from './Card' +import Row from './Row' import ModalUpdateTexture from './ModalUpdateTexture' const PlayersManagement: React.FC = () => { @@ -15,9 +18,17 @@ const PlayersManagement: React.FC = () => { const [page, setPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [isLoading, setIsLoading] = useState(false) + const isLargeScreen = useIsLargeScreen() + const [isTableMode, setIsTableMode] = useState(false) const [query, setQuery] = useState('') const [textureUpdating, setTextureUpdating] = useState(-1) + useEffect(() => { + if (isLargeScreen) { + setIsTableMode(true) + } + }, [isLargeScreen]) + const getPlayers = async () => { setIsLoading(true) const { data, last_page }: Paginator = await fetch.get( @@ -36,6 +47,10 @@ const PlayersManagement: React.FC = () => { getPlayers() }, [page]) + const handleModeChange = (event: React.ChangeEvent) => { + setIsTableMode(event.target.value === 'table') + } + const handleQueryChange = (event: React.ChangeEvent) => { setQuery(event.target.value) } @@ -147,7 +162,7 @@ const PlayersManagement: React.FC = () => { return (
-
+
{
+
+ + +
-
- {isLoading ? ( + {isLoading ? ( +
- ) : players.length === 0 ? ( -
No players.
- ) : ( -
- {players.map((player, i) => ( - handleUpdateName(player, i)} - onUpdateOwner={() => handleUpdateOwner(player, i)} - onUpdateTexture={() => setTextureUpdating(i)} - onDelete={() => handleDelete(player)} - /> - ))} -
- )} -
+
+ ) : players.length === 0 ? ( +
No players.
+ ) : isTableMode ? ( +
+ + + + + + + + + + + + + {players.map((player, i) => ( + handleUpdateName(player, i)} + onUpdateOwner={() => handleUpdateOwner(player, i)} + onUpdateTexture={() => setTextureUpdating(i)} + onDelete={() => handleDelete(player)} + /> + ))} + +
PID{t('general.player.player-name')}{t('general.player.owner')}{t('general.player.previews')}{t('general.player.last-modified')}{t('admin.operationsTitle')}
+
+ ) : ( +
+ {players.map((player, i) => ( + handleUpdateName(player, i)} + onUpdateOwner={() => handleUpdateOwner(player, i)} + onUpdateTexture={() => setTextureUpdating(i)} + onDelete={() => handleDelete(player)} + /> + ))} +
+ )}
diff --git a/resources/assets/tests/views/admin/PlayersManagement.test.tsx b/resources/assets/tests/views/admin/PlayersManagement.test.tsx index 486b61e4..3a58963a 100644 --- a/resources/assets/tests/views/admin/PlayersManagement.test.tsx +++ b/resources/assets/tests/views/admin/PlayersManagement.test.tsx @@ -27,6 +27,14 @@ function createPaginator(data: Player[]): Paginator { } } +beforeAll(() => { + Object.assign(window, { innerWidth: 500 }) +}) + +afterAll(() => { + Object.assign(window, { innerWidth: 1024 }) +}) + test('search players', async () => { fetch.get.mockResolvedValue(createPaginator([])) @@ -360,3 +368,66 @@ describe('delete player', () => { expect(queryByText(fixture.name)).toBeInTheDocument() }) }) + +describe('table mode', () => { + beforeEach(() => { + fetch.get.mockResolvedValue(createPaginator([fixture])) + }) + + it('large screen', async () => { + Object.assign(window, { innerWidth: 1024 }) + + const { queryByText } = render() + + await waitFor(() => expect(fetch.get).toBeCalled()) + expect(queryByText(t('admin.operationsTitle'))).toBeInTheDocument() + + Object.assign(window, { innerWidth: 500 }) + }) + + it('update player name', async () => { + const { getByText, getByTitle, queryByText } = render() + + await waitFor(() => expect(fetch.get).toBeCalled()) + fireEvent.click(getByTitle('Table Mode')) + fireEvent.click(getByTitle(t('admin.changePlayerName'))) + expect(queryByText(t('admin.changePlayerNameNotice'))).toBeInTheDocument() + + fireEvent.click(getByText(t('general.cancel'))) + }) + + it('update owner', async () => { + const { getByText, getByTitle, queryByText } = render() + + await waitFor(() => expect(fetch.get).toBeCalled()) + fireEvent.click(getByTitle('Table Mode')) + fireEvent.click(getByTitle(t('admin.changeOwner'))) + expect(queryByText(t('admin.changePlayerOwner'))).toBeInTheDocument() + + fireEvent.click(getByText(t('general.cancel'))) + }) + + it('update texture', async () => { + const { getByText, getByTitle, queryByPlaceholderText } = render( + , + ) + + await waitFor(() => expect(fetch.get).toBeCalled()) + fireEvent.click(getByTitle('Table Mode')) + fireEvent.click(getByText(t('admin.changeTexture'))) + expect(queryByPlaceholderText(t('admin.pidNotice'))).toBeInTheDocument() + + fireEvent.click(getByText(t('general.cancel'))) + }) + + it('delete player', async () => { + const { getByText, getByTitle, queryByText } = render() + + await waitFor(() => expect(fetch.get).toBeCalled()) + fireEvent.click(getByTitle('Table Mode')) + fireEvent.click(getByText(t('admin.deletePlayer'))) + expect(queryByText(t('admin.deletePlayerNotice'))).toBeInTheDocument() + + fireEvent.click(getByText(t('general.cancel'))) + }) +})