Skip to content

Commit

Permalink
front: cart page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kosei805 committed Nov 13, 2024
1 parent c69a3fc commit a6caf3b
Show file tree
Hide file tree
Showing 11 changed files with 441 additions and 3 deletions.
29 changes: 29 additions & 0 deletions frontend/app/components/cart/CartCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Card } from '@mantine/core';
import { CartProps } from '~/stores/cartAtom';
import BookCardThumbnail from '../books/BookCardThumbnail';
import CartCardHeader from './CartCardHeader';

interface CartCardProps {
book: CartProps;
}

const CartCard = ({ book }: CartCardProps) => {
return (
<Card shadow="sm" radius="md" pb="xs" withBorder>
<Card.Section withBorder inheritPadding>
<CartCardHeader
id={book.id}
stock={book.stock}
title={book.title}
volume={book.volume}
thumbnail={book.thumbnail}
/>
</Card.Section>
<Card.Section withBorder inheritPadding py="xs">
<BookCardThumbnail id={book.id} thumbnail={book.thumbnail} />
</Card.Section>
</Card>
);
};

export default CartCard;
81 changes: 81 additions & 0 deletions frontend/app/components/cart/CartCardHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Checkbox, Group } from '@mantine/core';
import { useAtom } from 'jotai';
import type { SelectedBookProps } from '~/stores/bookAtom';
import { cartAtom, selectedCartBooksAtom } from '~/stores/cartAtom';
import CartCardNumberInput from './CartCardNumberInput';

interface CartCardHeaderProps {
id: number;
stock: number;
title: string;
volume: number;
thumbnail?: string;
}

const CartCardHeader = ({
id,
stock,
title,
volume,
thumbnail,
}: CartCardHeaderProps) => {
const [cart, setCart] = useAtom(cartAtom);
const [selectedCartBook, setSelectedCartBook] = useAtom(
selectedCartBooksAtom,
);

// 該当する本のvolumeを変更する
const handleChangeVolume = (id: number, value: number) => {
setCart(
cart.map((element) => {
if (element.id === id) {
return {
id: element.id,
stock: element.stock,
title: element.title,
thumbnail: element.thumbnail,
volume: value,
};
}
return element;
}),
);
};

// 選択されている本のIDと表示する本のIDを比較する関数
const selectedCheck = (element: SelectedBookProps) => element.id === id;

const selectedBookAdd = () => {
// チェックボックスの状態が変化した時に
if (selectedCartBook.some(selectedCheck)) {
// すでに選択されていた場合は選択を外す
setSelectedCartBook(
selectedCartBook.filter((element) => element.id !== id),
);
} else {
// 選択されていなかった場合は選択する
setSelectedCartBook([
...selectedCartBook,
{ id, stock, title, thumbnail, volume: 1 },
]);
}
};

return (
<Group justify="space-between" py={10}>
<Checkbox
value={id}
checked={selectedCartBook.some(selectedCheck)}
onChange={selectedBookAdd}
/>
<CartCardNumberInput
id={id}
stock={stock}
volume={volume}
handleChangeVolume={handleChangeVolume}
/>
</Group>
);
};

export default CartCardHeader;
40 changes: 40 additions & 0 deletions frontend/app/components/cart/CartCardNumberInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Group, Select, Text } from '@mantine/core';
import { range } from '@mantine/hooks';

interface CartCardHeaderBadgeProps {
id: number;
stock: number;
volume: number;
handleChangeVolume: (id: number, value: number) => void;
}

const CartCardNumberInput = ({
id,
stock,
volume,
handleChangeVolume,
}: CartCardHeaderBadgeProps) => {
const stockList = range(0, stock);
const dataList = stock >= volume ? stockList : [...stockList, volume];
const strList = dataList.map((data) => data.toString());

const handleOnChange = (volume: string | null) => {
if (!volume) return;
const numVolume = Number(volume);
handleChangeVolume(id, numVolume);
};
return (
<Group justify="flex-end" w="70%">
<Text>冊数</Text>
<Select
w="50%"
data={strList}
value={String(volume)}
error={volume > stock}
onChange={(value) => handleOnChange(value)}
/>
</Group>
);
};

export default CartCardNumberInput;
31 changes: 31 additions & 0 deletions frontend/app/components/cart/CartCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ScrollArea, SimpleGrid } from '@mantine/core';
import { useAtom } from 'jotai';
import { cartAtom } from '~/stores/cartAtom';
import CartCard from './CartCard';

const CartCards = () => {
const [cart] = useAtom(cartAtom);

return (
<ScrollArea h="70dh">
<SimpleGrid
type="container"
cols={{
base: 2,
'500px': 3,
'800px': 4,
'1100px': 5,
'1400px': 6,
'1700px': 7,
}}
spacing={{ base: 10, '300px': 'xl' }}
>
{cart.map((book, index) => (
<CartCard key={index} book={book} />
))}
</SimpleGrid>
</ScrollArea>
);
};

export default CartCards;
30 changes: 30 additions & 0 deletions frontend/app/components/cart/CartListComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Stack } from '@mantine/core';
import CartTitle from './CartTitle';
import CartCards from './CartCards';
import CartSelectedDialog from './CartSelectedDialog';
import { useAtom } from 'jotai';
import { cartAtom } from '~/stores/cartAtom';
import NoCartComponent from './NoCartComponent';

interface CartListComponentProps {
handleLoanPatch: () => void;
}

const CartListComponent = ({ handleLoanPatch }: CartListComponentProps) => {
const [cart] = useAtom(cartAtom);
return (
<Stack bg="var(--mantine-color-body)" align="stretch" justify="flex-start">
<CartTitle />
{cart.length == 0 ? (
<NoCartComponent />
) : (
<>
<CartCards />
<CartSelectedDialog handleLoanPatch={handleLoanPatch} />
</>
)}
</Stack>
);
};

export default CartListComponent;
54 changes: 54 additions & 0 deletions frontend/app/components/cart/CartSelectedDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Button, Dialog, Stack } from '@mantine/core';
import { useAtom } from 'jotai';
import { cartAtom, selectedCartBooksAtom } from '~/stores/cartAtom';
import { removeBooksFromCart } from '~/utils/cart';

interface CartSelectedDialogProps {
handleLoanPatch: () => void;
}

const CartSelectedDialog = ({ handleLoanPatch }: CartSelectedDialogProps) => {
const [selectedCartBook, setSelectedCartBook] = useAtom(
selectedCartBooksAtom,
);
const [cart, setCart] = useAtom(cartAtom);

return (
<Dialog
opened={selectedCartBook.length > 0}
onClose={() => setSelectedCartBook([])}
>
<Stack
bg="var(--mantine-color-body)"
align="stretch"
justify="center"
gap="md"
>
<Button fz="xs" color="blue" onClick={handleLoanPatch}>
借りる
</Button>
<Button
fz="xs"
color="red"
onClick={() => {
setCart(removeBooksFromCart(cart, selectedCartBook));
setSelectedCartBook([]);
}}
>
カートから削除する
</Button>
<Button
fz="xs"
variant="light"
bd="solid 2px"
onClick={() => setSelectedCartBook([])}
>
選択を解除する
</Button>
</Stack>
</Dialog>
);
return <div>CartSelectedDialog</div>;
};

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

const CartTitle = () => {
return (
<Center>
<Group justify="center" align="center">
<FaShoppingCart size="3.5ch" />
<Title order={1}>貸出カート</Title>
</Group>
</Center>
);
};

export default CartTitle;
19 changes: 19 additions & 0 deletions frontend/app/components/cart/NoCartComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Anchor, Blockquote, Center } from '@mantine/core';
import { FaInfoCircle } from 'react-icons/fa';

interface NoCartComponentProps {
color?: string;
}

const NoCartComponent = ({ color }: NoCartComponentProps) => {
return (
<Center h="70dh" w="100%">
<Blockquote color={color ?? 'blue'} icon={<FaInfoCircle />} mt="xl">
カートに本が入っていません。蔵書一覧は
<Anchor href="/home">こちら</Anchor>から。
</Blockquote>
</Center>
);
};

export default NoCartComponent;
Loading

0 comments on commit a6caf3b

Please sign in to comment.