学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Django3.2 | 26 | QRオーダーシステムの構築 | メニューアイテム更新

[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 | メニューアイテム削除]