Newer
Older
2024-hinata / 情報システム / app.js
@hinata0428 hinata0428 on 16 May 13 KB add
const { useState, useEffect } = React;

// メインアプリケーションコンポーネント
function App() {
  const [step, setStep] = useState(1); // 1: テーブル選択, 2: キャラクター選択, 3: 寿司注文, 4: 履歴表示
  const [selectedTable, setSelectedTable] = useState(null);
  const [selectedCharacter, setSelectedCharacter] = useState(null);
  const [cart, setCart] = useState([]);
  const [showOrderComplete, setShowOrderComplete] = useState(false);
  const [orderDetails, setOrderDetails] = useState(null);

  // 購入履歴
  const [orderHistory, setOrderHistory] = useState([]);
  const [historyCharacterId, setHistoryCharacterId] = useState('');
  const [isHistoryLoading, setIsHistoryLoading] = useState(false);

  // 画面切り替え関数
  const nextStep = () => setStep(step + 1);
  const prevStep = () => setStep(step - 1);

  // カートに商品を追加する関数
  const addToCart = (item, quantity) => {
    const existingItemIndex = cart.findIndex(
      cartItem => cartItem.id === item.id
    );

    if (existingItemIndex !== -1) {
      const updatedCart = [...cart];
      updatedCart[existingItemIndex].quantity = quantity;
      if (quantity === 0) {
        updatedCart.splice(existingItemIndex, 1);
      }
      setCart(updatedCart);
    } else if (quantity > 0) {
      setCart([...cart, { ...item, quantity }]);
    }
  };

  // 合計金額を計算する関数
  const calculateTotal = () => {
    return cart.reduce((total, item) => {
      return total + (item.price * item.quantity);
    }, 0);
  };

  // 履歴取得
  const fetchOrderHistory = async (characterId = null) => {
    setIsHistoryLoading(true);
    try {
      const res = await window.API.getOrderHistory(characterId);
      if (res.status === 'success') {
        setOrderHistory(res.data);
      } else {
        setOrderHistory([]);
      }
    } catch (e) {
      setOrderHistory([]);
    }
    setIsHistoryLoading(false);
  };

  // 履歴リセット
  const handleResetHistory = async () => {
    if (!window.confirm('本当に履歴をリセットしますか?')) return;
    try {
      const res = await window.API.resetOrderHistory();
      alert(res.data && res.data.message ? res.data.message : 'リセットしました');
      fetchOrderHistory();
    } catch (e) {
      alert('リセット失敗');
    }
  };

  // 注文履歴画面へ
  const goToHistory = () => {
    setStep(4);
    fetchOrderHistory();
  };

  // 注文履歴キャラクター絞り込み
  const handleCharacterFilterChange = (e) => {
    const cid = e.target.value;
    setHistoryCharacterId(cid);
    fetchOrderHistory(cid ? cid : null);
  };

  // 現在のステップに応じたコンポーネントを表示
  const renderStep = () => {
    switch (step) {
      case 1:
        return <TableSelection
          selectedTable={selectedTable}
          setSelectedTable={setSelectedTable}
          onNext={nextStep}
          onHistory={goToHistory}
        />;
      case 2:
        return <CharacterSelection
          selectedCharacter={selectedCharacter}
          setSelectedCharacter={setSelectedCharacter}
          onPrev={prevStep}
          onNext={nextStep}
        />;
      case 3:
        return <SushiOrder
          cart={cart}
          addToCart={addToCart}
          calculateTotal={calculateTotal}
          onPrev={prevStep}
          selectedTable={selectedTable}
          selectedCharacter={selectedCharacter}
          showOrderComplete={setShowOrderComplete}
          setOrderDetails={setOrderDetails}
        />;
      case 4:
        return <OrderHistory
          orderHistory={orderHistory}
          characters={window.DataModel.characters}
          characterId={historyCharacterId}
          onCharacterChange={handleCharacterFilterChange}
          onBack={() => setStep(1)}
          onReset={handleResetHistory}
          isLoading={isHistoryLoading}
        />;
      default:
        return <div>エラーが発生しました</div>;
    }
  };

  // 注文完了モーダル
  const renderOrderCompleteModal = () => {
    if (!showOrderComplete || !orderDetails) return null;

    const characterName = window.DataModel.getCharacterName(orderDetails.character);

    return (
      <div className="modal-overlay">
        <div className="modal-content">
          <div className="modal-header">
            <h2>ご注文ありがとうございます!</h2>
          </div>
          <div className="modal-body">
            <p><strong>テーブル番号:</strong> {orderDetails.table}</p>
            <p><strong>キャラクター:</strong> {characterName}</p>
            <h3>ご注文内容</h3>
            {orderDetails.items.map(item => (
              <div key={item.id} className="cart-item">
                <span>{item.name} × {item.quantity}</span>
                <span>{(item.price * item.quantity).toLocaleString()}円</span>
              </div>
            ))}
            <div className="total-price">
              合計金額: {orderDetails.total.toLocaleString()}円
            </div>
          </div>
          <button
            className="button"
            onClick={() => {
              setShowOrderComplete(false);
              setCart([]);
            }}
          >
            メニューに戻る
          </button>
        </div>
      </div>
    );
  };

  return (
    <div>
      <header>
        <h1>寿司注文システム</h1>
      </header>
      <div className="container">
        {renderStep()}
        {renderOrderCompleteModal()}
      </div>
    </div>
  );
}

// テーブル選択コンポーネント
function TableSelection({ selectedTable, setSelectedTable, onNext, onHistory }) {
  const tables = window.DataModel.getTables();

  const handleTableSelect = (tableId) => {
    const table = tables.find(t => t.id === tableId);
    if (table.status === '空席') {
      setSelectedTable(tableId);
    }
  };

  return (
    <div className="card">
      <h2>テーブルを選択してください</h2>
      <div className="table-grid">
        {tables.map(table => (
          <div
            key={table.id}
            className={`table-item ${selectedTable === table.id ? 'selected' : ''} ${table.status === '使用中' ? 'disabled' : ''}`}
            onClick={() => handleTableSelect(table.id)}
            style={{ opacity: table.status === '使用中' ? 0.5 : 1 }}
          >
            <h3>テーブル {table.id}</h3>
            <p>{table.status}</p>
          </div>
        ))}
      </div>
      <div className="button-group">
        <button className="button" onClick={onHistory}>
          購入履歴を見る
        </button>
        <button
          className="button"
          onClick={onNext}
          disabled={!selectedTable}
          style={{ opacity: !selectedTable ? 0.5 : 1 }}
        >
          次へ
        </button>
      </div>
    </div>
  );
}

// キャラクター選択コンポーネント
function CharacterSelection({ selectedCharacter, setSelectedCharacter, onPrev, onNext }) {
  const characters = window.DataModel.characters;

  return (
    <div className="card">
      <h2>キャラクターを選択してください</h2>
      <div className="character-grid">
        {characters.map(character => (
          <div
            key={character.id}
            className={`character-item ${selectedCharacter === character.id ? 'selected' : ''}`}
            onClick={() => setSelectedCharacter(character.id)}
          >
            <img src={character.image} alt={character.name} className="character-image" />
            <h3>{character.name}</h3>
            <p>{character.description}</p>
          </div>
        ))}
      </div>
      <div className="button-group">
        <button className="button back-button" onClick={onPrev}>
          戻る
        </button>
        <button
          className="button"
          onClick={onNext}
          disabled={!selectedCharacter}
          style={{ opacity: !selectedCharacter ? 0.5 : 1 }}
        >
          次へ
        </button>
      </div>
    </div>
  );
}

// 寿司注文コンポーネント
function SushiOrder({ cart, addToCart, calculateTotal, onPrev, selectedTable, selectedCharacter, showOrderComplete, setOrderDetails }) {
  const [quantities, setQuantities] = useState({});

  const menuItems = window.DataModel.menuItems;

  const handleQuantityChange = (itemId, newQuantity) => {
    if (newQuantity >= 0) {
      setQuantities({
        ...quantities,
        [itemId]: newQuantity
      });

      const item = menuItems.find(item => item.id === itemId);
      addToCart(item, newQuantity);
    }
  };

  const getQuantity = (itemId) => {
    const cartItem = cart.find(item => item.id === itemId);
    return cartItem ? cartItem.quantity : 0;
  };

  const handleOrder = async () => {
    const orderData = {
      table: selectedTable,
      character: selectedCharacter,
      items: [...cart],
      total: calculateTotal(),
      time: new Date().toLocaleString()
    };
    try {
      await window.API.sendOrder(orderData);
      setOrderDetails(orderData);
      showOrderComplete(true);
    } catch (e) {
      alert('注文送信に失敗しました');
    }
  };

  return (
    <div>
      <div className="card">
        <h2>メニューから選択してください</h2>
        <div className="menu-grid">
          {menuItems.map(item => (
            <div key={item.id} className="menu-item">
              <img src={item.image} alt={item.name} className="menu-image" />
              <h3>{item.name}</h3>
              <p>{item.price.toLocaleString()}円</p>
              <div className="quantity-control">
                <button
                  className="quantity-btn"
                  onClick={() => handleQuantityChange(item.id, getQuantity(item.id) - 1)}
                >
                  -
                </button>
                <input
                  type="number"
                  className="quantity-input"
                  value={getQuantity(item.id)}
                  onChange={(e) => handleQuantityChange(item.id, parseInt(e.target.value) || 0)}
                  min="0"
                />
                <button
                  className="quantity-btn"
                  onClick={() => handleQuantityChange(item.id, getQuantity(item.id) + 1)}
                >
                  +
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>

      <div className="cart-section">
        <h3>注文内容</h3>
        <div className="cart-items">
          {cart.length === 0 ? (
            <p>カートに商品がありません</p>
          ) : (
            cart.map(item => (
              <div key={item.id} className="cart-item">
                <span>{item.name} × {item.quantity}</span>
                <span>{(item.price * item.quantity).toLocaleString()}円</span>
              </div>
            ))
          )}
        </div>
        <div className="total-price">
          合計金額: {calculateTotal().toLocaleString()}円
        </div>
        <div className="button-group">
          <button className="button back-button" onClick={onPrev}>
            戻る
          </button>
          <button
            className="button"
            onClick={handleOrder}
            disabled={cart.length === 0}
            style={{ opacity: cart.length === 0 ? 0.5 : 1 }}
          >
            注文確定
          </button>
        </div>
      </div>
    </div>
  );
}

// 購入履歴表示コンポーネント
function OrderHistory({ orderHistory, characters, characterId, onCharacterChange, onBack, onReset, isLoading }) {
  return (
    <div className="card">
      <h2>購入履歴</h2>
      <div style={{ marginBottom: 10 }}>
        <label>
          キャラクター絞り込み:
          <select value={characterId} onChange={onCharacterChange} style={{ marginLeft: 8 }}>
            <option value="">全キャラクター</option>
            {characters.map(c => (
              <option key={c.id} value={c.id}>{c.name}</option>
            ))}
          </select>
        </label>
      </div>
      <button className="button" onClick={onBack}>メニューに戻る</button>
      <button className="button" style={{ marginLeft: 8 }} onClick={onReset}>履歴リセット</button>
      <div style={{ marginTop: 20 }}>
        {isLoading ? (
          <p>読込中...</p>
        ) : orderHistory.length === 0 ? (
          <p>履歴がありません</p>
        ) : (
          orderHistory.map(order => (
            <div key={order.id} style={{ borderBottom: '1px solid #eee', marginBottom: 12, paddingBottom: 8 }}>
              <div>
                <strong>注文日時:</strong> {order.order_time}
                <span style={{ marginLeft: 12 }}>
                  <strong>キャラクター:</strong> {window.DataModel.getCharacterName(order.character_id)}
                </span>
                <span style={{ marginLeft: 12 }}>
                  <strong>テーブル:</strong> {order.table_number}
                </span>
                <span style={{ marginLeft: 12 }}>
                  <strong>合計:</strong> {order.total_price}円
                </span>
              </div>
              <div style={{ marginTop: 5, marginLeft: 12 }}>
                {order.items && order.items.length > 0 ? (
                  order.items.map(item =>
                    <span key={item.id} style={{ marginRight: 8 }}>
                      {item.item_name}×{item.quantity}
                    </span>
                  )
                ) : null}
              </div>
            </div>
          ))
        )}
      </div>
    </div>
  );
}

// アプリケーションをレンダリング
ReactDOM.render(<App />, document.getElementById('root'));