diff --git a/backend/src/main/java/com/example/todoapp/model/Stocks.java b/backend/src/main/java/com/example/todoapp/model/Stocks.java index 36ed9d9..41f5d02 100644 --- a/backend/src/main/java/com/example/todoapp/model/Stocks.java +++ b/backend/src/main/java/com/example/todoapp/model/Stocks.java @@ -100,7 +100,7 @@ public class Stocks { /** * 賞味期限 */ - @Column(nullable = false) + @Column(nullable = true) private LocalDate expDate; } \ No newline at end of file diff --git a/frontend/src/components/BuyExpDateSelect.tsx b/frontend/src/components/BuyExpDateSelect.tsx index 9cf1a27..486b5fd 100644 --- a/frontend/src/components/BuyExpDateSelect.tsx +++ b/frontend/src/components/BuyExpDateSelect.tsx @@ -16,7 +16,7 @@ const validateBuyDate = ((buyDate: string) => { }) // 日付文字列で早いほうを返す(空でないものは除く) -const validateExpDate = ((buyDate: string, expDate: string) => { +const validateExpDate = ((buyDate: string, expDate: string | null) => { // console.log('validateExpDate:', buyDate, expDate) if (!expDate) { // 賞味期限が未設定の場合そのまま未設定にする return ''; @@ -31,8 +31,8 @@ const BuyExpDateSelect = ({ newStock, setNewStock, }: { - newStock: {buyDate: string, expDate: string}, - setNewStock: (tobuy: {buyDate: string, expDate: string}) => void, + newStock: {buyDate: string, expDate: string | null}, + setNewStock: (tobuy: {buyDate: string, expDate: string | null}) => void, }) => { {/* 購入日・消費期限を横並びに */ } @@ -63,7 +63,7 @@ const BuyExpDateSelect = ({ fullWidth value={validateExpDate(newStock.buyDate, newStock.expDate)} onChange={(e) => - setNewStock({ ...newStock, expDate: e.target.value }) + setNewStock({ ...newStock, expDate: e.target.value ?? null }) } InputLabelProps={{ shrink: true }} InputProps={{ diff --git a/frontend/src/pages/StockPage.tsx b/frontend/src/pages/StockPage.tsx index 74bbf62..6f39e54 100644 --- a/frontend/src/pages/StockPage.tsx +++ b/frontend/src/pages/StockPage.tsx @@ -108,7 +108,7 @@ const StockPage: React.FC = () => { newStock.stuffId = null; } if (isNaN(newStock.amount)) return; - if (isNaN(newStock.price)) return; + if (newStock.price === null || isNaN(newStock.price)) return; if (newStock.buyAmount !== null && isNaN(newStock.buyAmount)) { newStock.buyAmount = null; @@ -118,23 +118,25 @@ const StockPage: React.FC = () => { console.log(newStock) // 購入日と消費・賞味期限の整合性チェック - 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; + 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); @@ -232,23 +234,25 @@ const StockPage: React.FC = () => { if (!editStock) return; const { stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate } = editStock; - // 購入日が消費・賞味期限より未来の場合はエラー表示 - 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; + 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 { @@ -368,21 +372,25 @@ const StockPage: React.FC = () => { {filteredStocks.map(stock => { - 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(); - const 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); + 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 ( { {stock.stuffName} {stock.amount} - {formatDate(stock.expDate)} + {stock.expDate && formatDate(stock.expDate)} ); diff --git a/frontend/src/pages/TaskListPage.tsx b/frontend/src/pages/TaskListPage.tsx index 8c9ad5d..12c9d2d 100644 --- a/frontend/src/pages/TaskListPage.tsx +++ b/frontend/src/pages/TaskListPage.tsx @@ -115,6 +115,7 @@ const TaskListPage: React.FC = () => { setToBuys(tobuys); } catch (error) { console.error(`${TOBUY_ERRORS.FETCH_FAILED}:`, error); + showErrorMessage(TOBUY_ERRORS.FETCH_FAILED); } }; @@ -144,6 +145,7 @@ const TaskListPage: React.FC = () => { fetchTasks(); // 削除後の買うもの一覧を再取得 } catch (error) { console.error(`${TOBUY_ERRORS.DELETE_FAILED}:`, error); + showErrorMessage(TOBUY_ERRORS.DELETE_FAILED); } }; @@ -175,6 +177,7 @@ const TaskListPage: React.FC = () => { } catch (error) { console.error(`${TOBUY_ERRORS.CREATE_FAILED}:`, error); + showErrorMessage(TOBUY_ERRORS.CREATE_FAILED); } }; @@ -197,6 +200,7 @@ const TaskListPage: React.FC = () => { fetchTasks(); // 作成後のタスク一覧を再取得 } catch (error) { console.error(`${TOBUY_ERRORS.CREATE_FAILED}:`, error); + showErrorMessage(TOBUY_ERRORS.CREATE_FAILED); } }; @@ -205,20 +209,32 @@ const TaskListPage: React.FC = () => { */ const handleBuy = async () => { try { + + // 今日の日付文字列(日付部分のみ) const today = new Date().toISOString().substring(0, 10); + const parsedPrice = parseInt(newStock.price, 10); console.log("newPrice:", newStock.price); console.log("parsedPrice: ", parsedPrice); + + // 購入価格のバリデーション if (isNaN(parsedPrice)) { showErrorMessage(GENERAL_ERRORS.INVALID_PRICE); return; - //setNewStock({ ...newStock, price: parsedPrice }); } + + // 購入数量のバリデーション const amount = parseInt(newStock.amount, 10); if (isNaN(amount)) { showErrorMessage(TOBUY_ERRORS.INVALID_BUYAMOUNT); return; } + + // 購入日のバリデーション(未設定の場合今日を設定) + if (!newStock.buyDate) { + newStock.buyDate = today; + } + await toBuyApi.buy({ tobuyId: selectedToBuyId, ...newStock, @@ -226,10 +242,14 @@ const TaskListPage: React.FC = () => { price: parsedPrice, lastUpdate: today }); //データベースに送信 + setOpenInfoDialog(false); + fetchTasks(); // 変更後後の買うもの一覧を再取得 + } catch (error) { console.error(`${TOBUY_ERRORS.BUY_FAILED}:`, error); + showErrorMessage(TOBUY_ERRORS.BUY_FAILED); } }; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 7ec10a3..df0e3e1 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -188,12 +188,20 @@ export const toBuyApi = { /** * 買うものリストの在庫登録(購入処理) */ - buy: async (req: { tobuyId: number, amount: number, price: number, shop: string, expDate: string, buyDate: string, lastUpdate: string }): Promise<{ result: boolean }> => { + buy: async (req: { + tobuyId: number, + amount: number, + price: number, + shop: string, + buyDate: string, + expDate: string | null, + lastUpdate: string + }): Promise<{ result: boolean }> => { console.log('/api/tobuy/buy request: ', req) - req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || '' - req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || '' + req.buyDate = parseDate(req.buyDate) || getToday(); + req.expDate = parseDate(req.expDate) const response = await fetch(`${API_BASE_URL}/api/tobuy/buy`, { method: 'POST', @@ -286,8 +294,8 @@ export const stockApi = { console.log("送信するデータ:", stock); // 送信前のデータ確認 - stock.buyDate = makeDateObject(stock.buyDate)?.toISOString()?.substring(0, 10) || '' - stock.expDate = makeDateObject(stock.expDate)?.toISOString()?.substring(0, 10) || '' + stock.buyDate = parseDate(stock.buyDate) || getToday(); + stock.expDate = parseDate(stock.expDate) console.log("変換後のデータ:", stock); // 日付変換後のデータ確認 @@ -317,8 +325,8 @@ export const stockApi = { updateStock: async (req: StockUpdateRequest): Promise<{ result: boolean; message: string }> => { // console.log('req: ', req) - req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || '' - req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || '' + req.buyDate = parseDate(req.buyDate) || getToday(); + req.expDate = parseDate(req.expDate) console.log('req: ', req) @@ -485,15 +493,22 @@ export const recipeApi = { }; -function makeDateObject(dateStr: String) { +function parseDate(dateStr: string | null) { // 例: '2025/06/15' または '2025-06-15' を '2025-06-15' に変換 - const parts = dateStr.split(/[-\/]/); // ハイフンかスラッシュで分割 - if (parts.length === 3) { - return new Date(parts[0] + '-' + parts[1] + '-' + parts[2]); + if (dateStr) { + const parts = dateStr.split(/[-\/]/); // ハイフンかスラッシュで分割 + if (parts.length === 3) { + const date = new Date(parts[0] + '-' + parts[1] + '-' + parts[2]); + return date.toISOString().substring(0, 10); + } } return null; // 無効な日付の場合 } +function getToday() { + return new Date().toISOString().substring(0, 10); +} + // /** // * (サンプル,実際には不要) diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 3dd7b7a..250d6c8 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -62,10 +62,10 @@ export interface StockUpdateRequest { stockId: number, amount: number, buyAmount: number | null, - price: number, + price: number | null, shop: string | null, buyDate: string, - expDate: string, + expDate: string | null, lastUpdate: string, } @@ -97,7 +97,7 @@ export interface NewStock { price: string, shop: string, buyDate: string, - expDate: string, + expDate: string | null, } /**