[25 | メニューアイテム詳細表示] << [ホーム] >> [27 | メニューアイテム削除]
「src/pages/Place.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/pages/Place.js】
import { IoMdArrowBack } from 'react-icons/io'; import { Row, Col, Button, Modal } from 'react-bootstrap'; import { useParams, useHistory } from 'react-router-dom'; import React, { useEffect, useState, useContext } from 'react'; import styled from 'styled-components'; import { fetchPlace } from '../apis'; import AuthContext from '../contexts/AuthContext'; import MainLayout from '../layouts/MainLayout'; import MenuItemForm from '../containers/MenuItemForm'; import MenuItem from '../components/MenuItem'; const Panel = styled.div` background-color: white; padding: 20px; border-radius: 5px; box-shadow: 1px 1px 10px rgba(0,0,0,0.05); `; const Place = () => { const [place, setPlace] = useState({}); const [menuItemFormShow, setMenuItemFormShow] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const showModal = () => setMenuItemFormShow(true); const hideModal = () => setMenuItemFormShow(false); const auth = useContext(AuthContext); const params = useParams(); const history = useHistory(); const onBack = () => history.push("/places"); const onFetchPlace = async () => { const json = await fetchPlace(params.id, auth.token); if(json) { setPlace(json); } }; useEffect(() => { onFetchPlace(); }, []); return ( <MainLayout> <Row> <Col lg={12}> <div className="mb-4"> <div className="d-flex align-items-center" Style="margin-left: 2rem; margin-top: 2rem;"> <Button variant="link" onClick={onBack}> <IoMdArrowBack size={25} color="black" /> </Button> <h3 className="mb-0 ml-2 mr-2">{place.name}</h3> </div> </div> </Col> <Col md={4}> <Panel> <MenuItemForm place={place} onDone={onFetchPlace} /> </Panel> </Col> <Col md={8}> {place?.categories?.map((category) => ( <div key={category.id} className="mb-5"> <h4 className="mb-0 mr-2 mb-4"> <b>{category.name}</b> </h4> {category.menu_items.map((item) => ( <MenuItem key={item.id} item={item} onEdit={() => { setSelectedItem(item); showModal() }} /> ))} </div> ))} </Col> </Row> <Modal show={menuItemFormShow} onHide={hideModal} centerd> <Modal.Body> <h4 className="text-center">メニューアイテム</h4> <MenuItemForm place={place} onDone={() => hideModal()} item={selectedItem} /> </Modal.Body> </Modal> </MainLayout> ) }; export default Place;
「src/containers/MenuItemForm.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/containers/MenuItemForm.js】
import React, { useState, useContext, useRef } from 'react'; import { Button, Form, Popover, Overlay } from 'react-bootstrap'; import { RiPlayListAddFill } from 'react-icons/ri'; import { toast } from 'react-toastify'; import { addCategory, addMenuItems } from '../apis'; import AuthContext from '../contexts/AuthContext'; import ImageDropzone from './ImageDropzone'; const MenuItemForm = ({ place, onDone, item = {} }) => { const [categoryName, setCategoryName] = useState(""); const [categoryFormShow, setCategoryFormShow] = useState(false); const [category, setCategory] = useState(item.category); const [name, setName] = useState(item.name); const [price, setPrice] = useState(item.price || 0); const [description, setDescription] = useState(item.description); const [image, setImage] = useState(item.image); const [isAvailable, setIsAvailable] = useState( item.is_available === undefined ? true : !!item.is_available ); const target = useRef(null); const auth = useContext(AuthContext); const onAddCategory = async () => { const json = await addCategory({name:categoryName, place:place.id}, auth.token); console.log(json); if(json) { toast(`カテゴリー: ${json.name} が作成されました。`, { type: "success"}); setCategory(json.id); setCategoryName(""); setCategoryFormShow(false); onDone(); } }; const onAddMenuItems = async () => { const json = await addMenuItems({ place: place.id, category, name, price, description, image, is_available: isAvailable }, auth.token); console.log(json); if (json) { toast(`メニューアイテム: ${json.name} が作成されました。`, { type: "success" }); setCategory(""); setName(""); setPrice(0); setDescription(""); setImage(""); setIsAvailable(true); onDone(); } } return ( <div> {/* カテゴリーフォーム */} <Form.Group> <Form.Label>カテゴリー</Form.Label> <div className="d-flex align-items-center"> <Form.Control as="select" value={category} onChange={(e) => setCategory(e.target.value)}> <option /> {place?.categories?.map((c) => ( <option key={c.id} value={c.id}> {c.name} </option> ))} </Form.Control> <Button ref={target} variant="link" onClick={() => setCategoryFormShow(true)}> <RiPlayListAddFill size={25} /> </Button> <Overlay show={categoryFormShow} target={target.current} placement="bottom" rootClose onHide={() => setCategoryFormShow(false)} > <Popover id="popover-contained"> <Popover.Title as="h3">カテゴリー</Popover.Title> <Popover.Content> <Form.Group> <Form.Control type="text" placeholder="カテゴリー名を入力" value={categoryName} onChange={(e) => setCategoryName(e.target.value)} /> </Form.Group> <Button variant="standard" block onClick={onAddCategory}> カテゴリー登録 </Button> </Popover.Content> </Popover> </Overlay> </div> </Form.Group> {/* メニューアイテムフォーム */} <Form.Group> <Form.Label>メニューアイテム名</Form.Label> <Form.Control type="text" placeholder="メニューアイテム名を入力" value={name} onChange={(e) => setName(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>価格</Form.Label> <Form.Control type="text" placeholder="価格を入力" value={price} onChange={(e) => setPrice(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>メニュー詳細</Form.Label> <Form.Control type="text" placeholder="メニュー詳細を入力" value={description} onChange={(e) => setDescription(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>画像</Form.Label> <ImageDropzone value={image} onChange={setImage} /> </Form.Group> <Form.Group> <Form.Check type="checkbox" label="利用可能" checked={isAvailable} onChange={(e) => setIsAvailable(e.target.checked)} /> </Form.Group> <Button variant="standard" block onClick={onAddMenuItems}> + メニューアイテム登録 </Button> <br/> </div> ); } export default MenuItemForm;
モダールの表示内容がメニューアイテムの内容とリンクするようになりました。
内容更新と削除ができるように実装します。
まずは「src/apis.js」ファイルに記述を追加します。
記述追加 【QRMenu/qrmenu_react/src/apis.js】95行目
import { toast } from 'react-toastify'; function request(path, {data = null, token = null, method = "GET" }) { return fetch(path, { method, headers: { Authorization: token ? `Token ${token}` : "", "Content-Type": "application/json", }, body: method !=="GET" && method !== "DELETE" ? JSON.stringify(data): null, }) .then((response) => { //もし成功したら if (response.ok) { if(method === "DELETE") { return true; } //toast.success("ログイン成功"); return response.json(); } //失敗 return response.json().then((json) => { //JSONエラー if (response.status === 400) { //toast.error("氏名もしくはパスワードに間違いがあります。"); const errors = Object.keys(json).map( (k) => `${(json[k].join(" "))}` ); throw new Error(errors.join(" ")); } throw new Error(JSON.stringify(json)); }) .catch((e) => { if (e.name === "SyntaxError") { throw new Error(response.statusText); } throw new Error(e); }) }) .catch((e) => { //全エラー toast(e.message, { type: "error" }); }) } export function signIn(username, password) { return request("/auth/token/login/", { data: {username, password}, method: "POST", }) } export function register(username, password) { return request("/auth/users/", { data: {username, password}, method: "POST", }) } export function fetchPlaces(token) { return request("/api/places/", {token}); } export function addPlace(data, token) { return request("/api/places/", {data, token, method: "POST" }); } export function uploadImage(image) { const formData = new FormData(); formData.append("file", image); formData.append("upload_preset", "qrmenu_photos"); return fetch("https://api.cloudinary.com/v1_1/dov57gocw/image/upload", { method: "POST", body: formData, }).then((response) => { return response.json(); }); } export function fetchPlace(id, token) { return request(`/api/places/${id}`, { token }); } export function addCategory(data, token) { return request("/api/categories/", {data, token, method: "POST"}); } export function addMenuItems(data, token) { return request("/api/menu_items/", {data, token, method: "POST"}); } export function updateMenuItem(id, data, token) { return request(`/api/menu_items/${id}`, { data, token, method: "PATCH"}); }
次に「src/containers/MenuItemForm.js」ファイルを編集します。
記述編集 【QRMenu/qrmenu_react/src/containers/MenuItemForm.js】
import React, { useState, useContext, useRef } from 'react'; import { Button, Form, Popover, Overlay } from 'react-bootstrap'; import { RiPlayListAddFill } from 'react-icons/ri'; import { toast } from 'react-toastify'; import { addCategory, addMenuItems, updateMenuItem } from '../apis'; import AuthContext from '../contexts/AuthContext'; import ImageDropzone from './ImageDropzone'; const MenuItemForm = ({ place, onDone, item = {} }) => { const [categoryName, setCategoryName] = useState(""); const [categoryFormShow, setCategoryFormShow] = useState(false); const [category, setCategory] = useState(item.category); const [name, setName] = useState(item.name); const [price, setPrice] = useState(item.price || 0); const [description, setDescription] = useState(item.description); const [image, setImage] = useState(item.image); const [isAvailable, setIsAvailable] = useState( item.is_available === undefined ? true : !!item.is_available ); const target = useRef(null); const auth = useContext(AuthContext); const onAddCategory = async () => { const json = await addCategory({name:categoryName, place:place.id}, auth.token); console.log(json); if(json) { toast(`カテゴリー: ${json.name} が作成されました。`, { type: "success"}); setCategory(json.id); setCategoryName(""); setCategoryFormShow(false); onDone(); } }; const onAddMenuItems = async () => { const json = await addMenuItems({ place: place.id, category, name, price, description, image, is_available: isAvailable }, auth.token); console.log(json); if (json) { toast(`メニューアイテム: ${json.name} が作成されました。`, { type: "success" }); setCategory(""); setName(""); setPrice(0); setDescription(""); setImage(""); setIsAvailable(true); onDone(); } } const onUpdateMenuItem = async () => { const json =await updateMenuItem( item.id, { place: place.id, category, name, price, description, image, is_available: isAvailable }, auth.token ); if (json) { console.log(json); toast(`メニューアイテム:${json.name}の内容が更新されました。`, { type: "success" }); setCategory(""); setName(""); setPrice(0); setDescription(""); setImage(""); setIsAvailable(false); onDone(); } } return ( <div> {/* カテゴリーフォーム */} <Form.Group> <Form.Label>カテゴリー</Form.Label> <div className="d-flex align-items-center"> <Form.Control as="select" value={category} onChange={(e) => setCategory(e.target.value)}> <option /> {place?.categories?.map((c) => ( <option key={c.id} value={c.id}> {c.name} </option> ))} </Form.Control> <Button ref={target} variant="link" onClick={() => setCategoryFormShow(true)}> <RiPlayListAddFill size={25} /> </Button> <Overlay show={categoryFormShow} target={target.current} placement="bottom" rootClose onHide={() => setCategoryFormShow(false)} > <Popover id="popover-contained"> <Popover.Title as="h3">カテゴリー</Popover.Title> <Popover.Content> <Form.Group> <Form.Control type="text" placeholder="カテゴリー名を入力" value={categoryName} onChange={(e) => setCategoryName(e.target.value)} /> </Form.Group> <Button variant="standard" block onClick={onAddCategory}> カテゴリー登録 </Button> </Popover.Content> </Popover> </Overlay> </div> </Form.Group> {/* メニューアイテムフォーム */} <Form.Group> <Form.Label>メニューアイテム名</Form.Label> <Form.Control type="text" placeholder="メニューアイテム名を入力" value={name} onChange={(e) => setName(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>価格</Form.Label> <Form.Control type="text" placeholder="価格を入力" value={price} onChange={(e) => setPrice(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>メニュー詳細</Form.Label> <Form.Control type="text" placeholder="メニュー詳細を入力" value={description} onChange={(e) => setDescription(e.target.value)} /> </Form.Group> <Form.Group> <Form.Label>画像</Form.Label> <ImageDropzone value={image} onChange={setImage} /> </Form.Group> <Form.Group> <Form.Check type="checkbox" label="利用可能" checked={isAvailable} onChange={(e) => setIsAvailable(e.target.checked)} /> </Form.Group> <Button variant="standard" block onClick={ item.id ? onUpdateMenuItem : onAddMenuItems} > { item.id ? "メニューアイテム内容更新" : "+ メニューアイテム登録"} </Button> <br/> </div> ); } export default MenuItemForm;
データ更新した時にページも自動でリロードされるようにします。
「src/pages/Place.js」ファイルに記述を追加します。
記述追加 【QRMenu/qrmenu_react/src/pages/Place.js】95行目
import { IoMdArrowBack } from 'react-icons/io'; import { Row, Col, Button, Modal } from 'react-bootstrap'; import { useParams, useHistory } from 'react-router-dom'; import React, { useEffect, useState, useContext } from 'react'; import styled from 'styled-components'; import { fetchPlace } from '../apis'; import AuthContext from '../contexts/AuthContext'; import MainLayout from '../layouts/MainLayout'; import MenuItemForm from '../containers/MenuItemForm'; import MenuItem from '../components/MenuItem'; const Panel = styled.div` background-color: white; padding: 20px; border-radius: 5px; box-shadow: 1px 1px 10px rgba(0,0,0,0.05); `; const Place = () => { const [place, setPlace] = useState({}); const [menuItemFormShow, setMenuItemFormShow] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const showModal = () => setMenuItemFormShow(true); const hideModal = () => setMenuItemFormShow(false); const auth = useContext(AuthContext); const params = useParams(); const history = useHistory(); const onBack = () => history.push("/places"); const onFetchPlace = async () => { const json = await fetchPlace(params.id, auth.token); if(json) { setPlace(json); } }; useEffect(() => { onFetchPlace(); }, []); return ( <MainLayout> <Row> <Col lg={12}> <div className="mb-4"> <div className="d-flex align-items-center" Style="margin-left: 2rem; margin-top: 2rem;"> <Button variant="link" onClick={onBack}> <IoMdArrowBack size={25} color="black" /> </Button> <h3 className="mb-0 ml-2 mr-2">{place.name}</h3> </div> </div> </Col> <Col md={4}> <Panel> <MenuItemForm place={place} onDone={onFetchPlace} /> </Panel> </Col> <Col md={8}> {place?.categories?.map((category) => ( <div key={category.id} className="mb-5"> <h4 className="mb-0 mr-2 mb-4"> <b>{category.name}</b> </h4> {category.menu_items.map((item) => ( <MenuItem key={item.id} item={item} onEdit={() => { setSelectedItem(item); showModal() }} /> ))} </div> ))} </Col> </Row> <Modal show={menuItemFormShow} onHide={hideModal} centerd> <Modal.Body> <h4 className="text-center">メニューアイテム</h4> <MenuItemForm place={place} onDone={() =>{ onFetchPlace(); hideModal() }} item={selectedItem} /> </Modal.Body> </Modal> </MainLayout> ) }; export default Place;
これでメニューアイテムの内容を更新できるようになりました。
↓↓クリックして頂けると励みになります。
[25 | メニューアイテム詳細表示] << [ホーム] >> [27 | メニューアイテム削除]