/** * テストページコンポーネント * 白紙の状態で表示されるテスト用のページ */ import React, { useState, useEffect } from 'react'; import { stockApi, stuffApi } from '../services/api'; import { Stock, StockUpdateRequest, Stuff } from '../types/types'; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material"; import { Container, Typography, Tooltip, Fab, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Box, Checkbox, FormControlLabel, FormGroup, FormControl, InputLabel, Select, MenuItem, } from '@mui/material'; import { GENERAL_ERRORS, STOCK_ERRORS } from '../constants/errorMessages'; import { Add as AddIcon } from '@mui/icons-material'; /*import DatePicker, { registerLocale } from 'react-datepicker'; import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート*/ import { useMessage } from '../components/MessageContext'; import BuyExpDateSelect from '../components/BuyExpDateSelect'; /*// 日付をyyyy-MM-dd形式で返す関数 const formatDateLocal = (date: Date) => { const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); return `${year}-${month}-${day}`; };*/ // 新規在庫の初期状態 const EMPTY_STOCK: Omit & { stuffId: number | null } & { newAddition: boolean } = { stuffId: null, stuffName: '', amount: 1, buyAmount: 1, price: 0, shop: '', lastUpdate: '', buyDate: new Date().toISOString(), expDate: '', category: '', newAddition: false, // 材料を新規作成するか否か // shop '', } // 日本語ロケールを登録 //registerLocale('ja', ja); const StockPage: React.FC = () => { const [stocks, setStocks] = useState([]); // セル選択の表示状態 const [selectedRow, setSelectedRow] = useState(null); // 追加ダイアログボックスの表示状態 const [isAddOpen, setIsAddOpen] = useState(false); // 在庫追加に使う状態 const [newStock, setNewStock] = useState(EMPTY_STOCK); const [stuffs, setStuffs] = useState([]); // 編集ダイアロボックスの表示状態 const [isEditOpen, setIsEditOpen] = useState(false); // 削除メッセージダイアログの表示状態 const [isDeleteOpen, setIsDeleteOpen] = useState(false); // 在庫の編集状態 const [editStock, setEditStock] = useState(null); const { showErrorMessage, showWarningMessage } = useMessage(); // コンポーネントマウント時にタスク一覧を取得 useEffect(() => { fetchStocks(); }, []); /** * APIから在庫一覧を取得する関数 * 取得したタスクをstate(tasks)に設定 */ const fetchStocks = async () => { try { const stocks = await stockApi.getStocks(); console.log('Stocks=', stocks) setStocks(stocks); } catch (error) { console.error(`${STOCK_ERRORS.FETCH_FAILED}:`, error); showErrorMessage(STOCK_ERRORS.FETCH_FAILED); } }; /** * 在庫リストに新規食材を作成するハンドラー */ const handleCreateStock = async () => { try { if (newStock.newAddition) { newStock.stuffId = null; } if (isNaN(newStock.amount)) return; if (newStock.price === null || isNaN(newStock.price)) return; if (newStock.buyAmount !== null && isNaN(newStock.buyAmount)) { newStock.buyAmount = null; } newStock.shop = newStock.shop || null; console.log(newStock) // 購入日と消費・賞味期限の整合性チェック if (newStock.expDate !== null) { const buy = new Date(newStock.buyDate); const exp = new Date(newStock.expDate); // 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得 const buyDateOnly = new Date(buy); buyDateOnly.setHours(0, 0, 0, 0); const expDateOnly = new Date(exp); expDateOnly.setHours(0, 0, 0, 0); // console.log("新規作成buy:", buy); // console.log("新規作成exp:", exp); // console.log("新規作成buyDateOnly:", buyDateOnly); // console.log("新規作成expDateOnly:", expDateOnly); if (buyDateOnly.getTime() > expDateOnly.getTime()) { // alert("購入日は消費・賞味期限より前の日付を設定してください。"); showErrorMessage('購入日は消費・賞味期限より前の日付を設定してください.'); return; } } const today = new Date().toISOString().substring(0, 10); const updatedStock = { ...newStock, lastUpdate: today }; // lastUpdate に today を設定 console.log("送信するデータ:", updatedStock); // 送信前のデータを確認 await stockApi.addStock(updatedStock); // 修正したオブジェクトを API に送信 // await stockApi.addStock(newStock); setIsAddOpen(false); // ダイアログを閉じる setNewStock(EMPTY_STOCK); // 入力内容をリセット fetchStocks(); // 作成後のタスク一覧を再取得 } catch (error) { console.error(`${STOCK_ERRORS.CREATE_FAILED}:`, error); showErrorMessage(STOCK_ERRORS.CREATE_FAILED); } }; /** * 在庫リストを編集するハンドラー */ const handleUpdateStock = async (request: StockUpdateRequest) => { try { await stockApi.updateStock(request); fetchStocks(); // 削除後の買うもの一覧を再取得 } catch (error) { console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error); showErrorMessage(STOCK_ERRORS.UPDATE_FAILED); } }; /** * 在庫を削除するハンドラー * 指定されたIDのタスクをAPIを通じて削除 */ const handleDeleteStock = async (stockId: number) => { try { await stockApi.deleteStock(stockId); fetchStocks(); // 削除後の買うもの一覧を再取得 } catch (error) { console.error(`${STOCK_ERRORS.DELETE_FAILED}:`, error); showErrorMessage(STOCK_ERRORS.DELETE_FAILED); } }; /** * カテゴリー選択 */ const onChangeCategory = async (category: string) => { setNewStock({ ...newStock, category }) const result = await stuffApi.getStuffs(category) setStuffs(result) } /** * 文字列(ISO 8601形式)をyyyy/MM/ddに変換する関数 */ const formatDate = (isoString: string): string => { const date = new Date(isoString); const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1 const day = date.getDate().toString().padStart(2, "0"); return `${year}/${month}/${day}`; }; /* Date型をyyyy/MM/ddに変換する関数 const formatDate = (date: Date): string => { const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1 const day = date.getDate().toString().padStart(2, "0"); return `${year}/${month}/${day}`; }; */ /** 追加ボタンを押したときにダイアログを開く */ const handleOpenAdd = () => { setIsAddOpen(true); }; /** 追加ダイアログを閉じる */ const handleCloseAdd = () => { setIsAddOpen(false); }; /** * セルを選択して編集画面 */ const handleRowClick = (stock: Stock) => { setSelectedRow(stock); // 行選択 setEditStock({ ...stock }); // 編集対象にセット setIsEditOpen(true); // 編集ダイアログを開く }; // 変更を適用. 数量に0を入力したとき、削除ダイアログに飛ぶ機能を追加 const handleApplyChanges = async () => { if (!editStock) return; const { stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate } = editStock; if (expDate !== null) { // 購入日が消費・賞味期限より未来の場合はエラー表示 const buy = new Date(buyDate); const exp = new Date(expDate); // 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得 const buyDateOnly = new Date(buy); buyDateOnly.setHours(0, 0, 0, 0); const expDateOnly = new Date(exp); expDateOnly.setHours(0, 0, 0, 0); // console.log("編集buy:", buy); // console.log("編集exp:", exp); // console.log("編集buyDateOnly:", buyDateOnly); // console.log("編集expDateOnly:", expDateOnly); if (buy > exp) { // alert("購入日は消費・賞味期限より前の日付を設定してください。"); showErrorMessage('購入日は消費・賞味期限より前の日付を設定してください.'); return; } } try { // Number型に変換した変数を用意 const numericAmount = Number(amount); const numericBuyAmount = Number(buyAmount); const numericPrice = Number(price); if (numericAmount === 0) { // 数量が 0 の場合は削除処理へ誘導 // setIsEditOpen(false); // 編集ダイアログを閉じる setSelectedRow(editStock); // 削除対象をセット setIsDeleteOpen(true); // 削除ダイアログを開く return; } if (!numericAmount || !numericBuyAmount) { showErrorMessage(GENERAL_ERRORS.INVALID_AMOUNT); return; } if (!numericPrice) { showErrorMessage(GENERAL_ERRORS.INVALID_PRICE); return; } const lastUpdate = new Date().toISOString().substring(0, 10); const updateRequest = { stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate, } console.log('updateRequest:', updateRequest); await handleUpdateStock(updateRequest); setSelectedRow(editStock); // 更新後に選択行を反映 fetchStocks(); // 最新データを取得 setSelectedRow(null); // 選択解除 } catch (error) { console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error); showErrorMessage(STOCK_ERRORS.UPDATE_FAILED); } setIsEditOpen(false); // 編集ダイアログを閉じる }; // ダイアログを開く際に `selectedRow` の値を `editStock` にコピー useEffect(() => { if (selectedRow) { setEditStock({ ...selectedRow }); } }, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映 // テキストフィールドの変更を検知 // 負の値を入力できないように書き換え const handleChange = (event: React.ChangeEvent) => { const { name, value } = event.target; // 数値項目に対して負の値をブロック const numericFields = ['amount', 'buyAmount', 'price']; const numericValue = Number(value); const isNumericField = numericFields.includes(name); if (isNumericField && numericValue < 0) { return; // 無視して更新しない } if (editStock) { setEditStock({ ...editStock, [name]: value, }); } }; /** 編集ダイアログを閉じる */ const handleCloseEdit = () => { setIsEditOpen(false); setSelectedRow(null); }; /** 削除ボタンを押したときにダイアログを開く */ const handleOpenDelete = () => { if (selectedRow) { setIsDeleteOpen(true); } else { // showWarningMessage("削除する食材を選択してください。"); showErrorMessage('削除する食材を選択してください.'); } }; /** 削除ダイアログを閉じる */ const handleCloseDelete = () => { setIsDeleteOpen(false); }; /** テーブルを表示する関数 */ const StockTable = (stocks: Stock[], categories: string[]) => { const filteredStocks = stocks.filter(stock => categories.includes(stock.category)); if (filteredStocks.length === 0) return null; return ( <> 食材名 数量 消費・賞味期限 {filteredStocks.map(stock => { let daysLeft = null; if (stock.expDate !== null) { const today = new Date(); const expDate = new Date(stock.expDate); // 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得 const todayDateOnly = new Date(today); todayDateOnly.setHours(0, 0, 0, 0); const expDateOnly = new Date(expDate); expDateOnly.setHours(0, 0, 0, 0); const timeDiff = expDateOnly.getTime() - todayDateOnly.getTime(); daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); // console.log("テーブルtoday:", today); // console.log("テーブルexp:", expDate); // console.log("テーブルtodayDateOnly:", todayDateOnly); // console.log("テーブルexpDateOnly:", expDateOnly); // console.log("日数差:", daysLeft); } return ( handleRowClick(stock)} style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }} > {stock.stuffName} {stock.amount} {stock.expDate && formatDate(stock.expDate)} ); })}
{/* 編集ダイアログ */} 在庫の編集 {editStock && ( <> {/* 材料の詳細 */} 【{editStock.stuffName}】 {/* 現在の数量フィールド */} { if (e.key === '-' || e.key === 'e' || e.key === 'E') { e.preventDefault(); } }} /> {/* 購入時数量フィールド */} { if (e.key === '-' || e.key === 'e' || e.key === 'E') { e.preventDefault(); } }} /> {/* 購入価格フィールド */} { if (e.key === '-' || e.key === 'e' || e.key === 'E') { e.preventDefault(); } }} /> {/* 購入店舗フィールド */} {/* 購入日・賞味期限入力 */} setEditStock({ ...editStock, buyDate, expDate })} /> )} {/* 削除ダイアログ */} 食材の削除 {selectedRow && ( <> 【{selectedRow.stuffName}】を削除します。 ⚠️ 注意: 削除すると復元できません。 )} ); }; return ( 在庫リスト {/* 在庫の食材追加ボタン */} 食材の追加 {/* 新規タスク作成ダイアログ */} 在庫に食材を追加 } label="食材を新規追加" checked={newStock.newAddition} onChange={(e) => setNewStock({ ...newStock, newAddition: (e.target as HTMLInputElement).checked })} /> {/*材料カテゴリ選択 */} カテゴリ {!newStock.newAddition && 材料名(選択) } {/* タスクタイトル入力フィールド */} {newStock.newAddition && setNewStock({ ...newStock, stuffName: e.target.value })} sx={{ marginBottom: 2 }} />} {/* 現在の数量入力フィールド */} { const value = e.target.value; const parsedValue = parseInt(value, 10); // 数値に変換 if (isNaN(parsedValue) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK setNewStock({ ...newStock, amount: parsedValue }); // number型で保存 } }} // sx={{ width: "50%" }} type="number" inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 /> {/* 購入時数量入力フィールド */} { const value = e.target.value; const parsedValue = parseInt(value, 10); // 数値に変換 if (isNaN(parsedValue) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK setNewStock({ ...newStock, buyAmount: parsedValue }); // number型で保存 } }} // sx={{ width: "50%" }} type="number" inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 /> {/* 購入価格入力フィールド */} { const value = e.target.value; const parsedValue = parseInt(value, 10); // 数値に変換 if (isNaN(parsedValue) || parsedValue >= 0) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は0以上のみOK setNewStock({ ...newStock, price: parsedValue }); // number型で保存 } }} // sx={{ width: "50%" }} type="number" inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 /> {/* 購入店舗入力フィールド */} setNewStock({ ...newStock, shop: e.target.value })} /> {/* 購入日・賞味期限入力 */} setNewStock({ ...newStock, buyDate, expDate })} /> {/* 在庫一覧リスト */} {/* 乳製品 */} 乳製品
{StockTable(stocks, ["乳製品"])}
{/* 肉・魚 */} 魚・肉
{StockTable(stocks, ["魚・肉"])}
{/* 野菜 */} 野菜
{StockTable(stocks, ["野菜"])}
{/* 調味料 */} 調味料
{StockTable(stocks, ["調味料"])}
{/* その他 */} その他
{StockTable(stocks, ["その他"])}
{/* フッターの高さと同じくらいに調整 */}
); }; export default StockPage;