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'));