Skip to content

Commit

Permalink
front: user list page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kosei805 committed Nov 14, 2024
1 parent 85cc374 commit d6c9278
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 0 deletions.
24 changes: 24 additions & 0 deletions frontend/app/components/users/UserDeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Button } from '@mantine/core';

interface UserDeleteButtonProps {
id: number;
handleDeleteUserButtonClick: (id: number) => void;
}

const UserDeleteButton = ({
id,
handleDeleteUserButtonClick,
}: UserDeleteButtonProps) => {
return (
<Button
color="red"
variant="light"
bd="solid 2px"
onClick={() => handleDeleteUserButtonClick(id)}
>
削除
</Button>
);
};

export default UserDeleteButton;
55 changes: 55 additions & 0 deletions frontend/app/components/users/UsersListComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Container, Stack } from '@mantine/core';
import UsersListTitle from './UsersListTitle';
import ContentsHeader from '../common/pagination/ContentsHeader';
import type { PaginationProps } from '~/types/paginatiion';
import PaginationComponent from '../common/pagination/PaginationComponent';
import ErrorComponent from '../common/error/ErrorComponent';
import { getUsersResponse } from 'client/client';
import UsersListTable from './UsersListTable';

interface UsersListComponentProps {
paginationProps: PaginationProps;
usersResponse: getUsersResponse;
handleDeleteUserButtonClick: (id: number) => void;
}

const UsersListComponent = ({
paginationProps,
usersResponse,
handleDeleteUserButtonClick,
}: UsersListComponentProps) => {
return (
<Container>
<Stack
bg="var(--mantine-color-body)"
align="center"
justify="center"
maw="100%"
>
<UsersListTitle />
<ContentsHeader
page={paginationProps.page}
limit={paginationProps.limit}
total={paginationProps.totalNum}
handleLimitChange={paginationProps.handleLimitChange}
/>
{usersResponse.status === 200 ? (
<UsersListTable
users={usersResponse.data.users}
handleDeleteUserButtonClick={handleDeleteUserButtonClick}
/>
) : (
<ErrorComponent message={'ユーザー情報を取得できませんでした。'} />
)}
<PaginationComponent
totalNum={paginationProps.totalNum}
page={paginationProps.page}
limit={paginationProps.limit}
handlePaginationChange={paginationProps.handlePaginationChange}
/>
</Stack>
</Container>
);
};

export default UsersListComponent;
37 changes: 37 additions & 0 deletions frontend/app/components/users/UsersListTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { rem, Table } from '@mantine/core';
import { GetUsers200UsersItem } from 'client/client.schemas';
import UserDeleteButton from './UserDeleteButton';

interface UsersTableProps {
users: GetUsers200UsersItem[];
handleDeleteUserButtonClick: (id: number) => void;
}

const UsersListTable = ({
users,
handleDeleteUserButtonClick,
}: UsersTableProps) => {
return (
<Table striped maw="50%">
<Table.Tbody>
{users.map((user) => (
<Table.Tr key={user.id}>
{user.id && (
<>
<Table.Td>{user.name}</Table.Td>
<Table.Td maw={rem(5)}>
<UserDeleteButton
id={user.id}
handleDeleteUserButtonClick={handleDeleteUserButtonClick}
/>
</Table.Td>
</>
)}
</Table.Tr>
))}
</Table.Tbody>
</Table>
);
};

export default UsersListTable;
15 changes: 15 additions & 0 deletions frontend/app/components/users/UsersListTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Center, Group, Title } from '@mantine/core';
import { FaUsers } from 'react-icons/fa';

const UsersListTitle = () => {
return (
<Center>
<Group justify="center" align="center">
<FaUsers size="3.5ch" />
<Title order={1}>ユーザー一覧</Title>
</Group>
</Center>
);
};

export default UsersListTitle;
144 changes: 144 additions & 0 deletions frontend/app/routes/home.users._index/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
ActionFunctionArgs,
json,
LoaderFunctionArgs,
redirect,
} from '@remix-run/cloudflare';
import { useFetcher, useLoaderData, useNavigate } from '@remix-run/react';
import { deleteUser, getUsers, getUsersResponse } from 'client/client';
import UsersListComponent from '~/components/users/UsersListComponent';
import { commitSession, getSession } from '~/services/session.server';
import { ActionResponse } from '~/types/response';

interface LoaderData {
usersResponse: getUsersResponse;
condition: {
page?: string;
limit?: string;
};
}

export const loader = async ({ request }: LoaderFunctionArgs) => {
// 検索条件を取得する
const url = new URL(request.url);
const page = url.searchParams.get('page') ?? undefined;
const limit = url.searchParams.get('limit') ?? undefined;
// ユーザー情報を取得する
const response = await getUsers({ page: page, limit: limit });

return json<LoaderData>({
usersResponse: response,
condition: {
page: page,
limit: limit,
},
});
};

export const action = async ({ request }: ActionFunctionArgs) => {
const session = await getSession(request.headers.get('Cookie'));
const formData = await request.formData();
const userId = String(formData.get('userId'));

// 未ログインの場合
if (!session.has('user')) {
session.flash('error', 'ログインしてください');
return redirect('/login', {
headers: {
'Set-Cookie': await commitSession(session),
},
});
}

const cookieHeader = [
`__Secure-user_id=${session.get('user')?.id};`,
`__Secure-session_token=${session.get('user')?.sessionToken}`,
].join('; ');

const response = await deleteUser(userId, {
headers: { Cookie: cookieHeader },
});

switch (response.status) {
case 204:
session.flash('success', 'ユーザーを削除しました');
if (Number(userId) === session.get('user')?.id) {
session.unset('user');
session.flash('success', 'ログアウトしました');
return redirect('/home', {
headers: {
'Set-Cookie': await commitSession(session),
},
});
}
break;
case 401:
session.flash('error', 'ログインしてください');
return redirect('/login', {
headers: {
'Set-Cookie': await commitSession(session),
},
});
case 404:
session.flash('error', 'ユーザーが見つかりませんでした');
break;
case 500:
session.flash('error', 'サーバーエラーが発生しました');
break;
}
return json<ActionResponse>(
{ method: 'DELETE', status: response.status },
{
headers: {
'Set-Cookie': await commitSession(session),
},
},
);
};

const UsersListPage = () => {
const { usersResponse, condition } = useLoaderData<typeof loader>();
const { page, limit } = condition;
const navigate = useNavigate();
const fetcher = useFetcher();
const handlePaginationChange = (newPage: number) => {
let url = '/home/users';
let initial = true;
if (limit) {
url =
initial === true ? `${url}?limit=${limit}` : `${url}&limit=${limit}`;
initial = false;
}
url =
initial === true ? `${url}?page=${newPage}` : `${url}&page=${newPage}`;
navigate(url);
};

const handleLimitChange = (newLimit: number) =>
navigate(`/home/users?limit=${newLimit}`);

const handleDeleteUserButtonClick = (id: number) => {
fetcher.submit(
{ userId: id },
{
method: 'DELETE',
},
);
};

return (
<UsersListComponent
paginationProps={{
handlePaginationChange: handlePaginationChange,
handleLimitChange: handleLimitChange,
page: page ? Number(page) : undefined,
limit: limit ? Number(limit) : undefined,
totalNum: usersResponse.data.totalUser,
}}
usersResponse={usersResponse}
handleDeleteUserButtonClick={handleDeleteUserButtonClick}
/>
);
};

export default UsersListPage;
7 changes: 7 additions & 0 deletions frontend/app/routes/home.users/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Outlet } from '@remix-run/react';

const UsersLayout = () => {
return <Outlet />;
};

export default UsersLayout;

0 comments on commit d6c9278

Please sign in to comment.