diff --git a/app/Http/Controllers/ParkDetailController.php b/app/Http/Controllers/ParkDetailController.php index 371802e..197f14f 100644 --- a/app/Http/Controllers/ParkDetailController.php +++ b/app/Http/Controllers/ParkDetailController.php @@ -2,35 +2,165 @@ namespace App\Http\Controllers; +use App\Http\Traits\AuthenticatesUser; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\DB; class ParkDetailController extends Controller { - public function show($park_id) + use AuthenticatesUser; + + /** + * 駐輪場詳細情報を取得(Ajax用) + * + * @param string $management_code + * @param int $park_id + * @return \Illuminate\Http\JsonResponse + */ + public function show($management_code, $park_id) { + if ($redirect = $this->handleSessionExpired('駐輪場詳細', false)) { + return $redirect; + } + + $userId = session('user_id'); + $management = session('management'); + if (!$management) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 運営元情報取得失敗 user_id=" . $userId); + return response()->json(['error' => 'Management not found'], 404); + } + + // マルチテナント対応:運営元に紐づく駐輪場のみ取得 + $park = $this->getParkInfo($park_id, $management->management_id); + if (!$park) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場情報取得失敗 park_id=" . $park_id . ", management_id=" . $management->management_id . ", user_id=" . $userId); + return response()->json(['error' => 'Park not found'], 404); + } + $park = DB::table('park')->where('park_id', $park_id)->first(); - $zones = DB::table('zone') - ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') - ->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') - ->select('zone.*', 'psection.psection_subject', 'ptype.ptype_subject') - ->where('zone.park_id', $park_id) - ->get(); + // ゾーン情報取得 + $zones = $this->getZones($park_id); - $reserves = DB::table('reserve') - ->join('zone', function ($join) { - $join->on('reserve.psection_id', '=', 'zone.psection_id') - ->on('reserve.ptype_id', '=', 'zone.ptype_id'); - }) - ->join('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') - ->where('reserve.park_id', $park_id) - ->where('reserve.valid_flag', 1) - ->select('reserve.*', 'ptype.ptype_subject') - ->get(); + // 予約情報取得 + $reserves = $this->getReserves($park_id); + // 車種別の基準台数集計 + $zoneStandardSum = $this->calculateZoneStandardSum($zones); + + // 車種別の空き台数計算 + $vacancyData = $this->calculateVacancy($zones, $reserves); + + // 駐輪場ごとの更新可能期間取得 + $parkGracePeriod = $this->getParkGracePeriod($park_id); + + \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " 駐輪場詳細取得成功 park_id=" . $park_id . ", management_id=" . $management->management_id . ", user_id=" . $userId); + + try { + $html = view('regular_contract.park_detail', compact( + 'park', + 'zones', + 'reserves', + 'zoneStandardSum', + 'vacancyData', + 'parkGracePeriod' + ))->render(); + + \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " ビュー生成成功 park_id=" . $park_id . ", HTML長=" . strlen($html)); + + + return response()->json(['html' => $html]); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " ビュー生成失敗 park_id=" . $park_id . ", error=" . $e->getMessage()); + return response()->json(['error' => 'View rendering failed'], 500); + } + } + + /** + * 駐輪場情報取得(マルチテナントフィルタ適用) + * + * なぜ: 運営元に紐づく駐輪場のみ取得するため + * + * @param int $parkId + * @param int $managementId + * @return object|null + */ + private function getParkInfo(int $parkId, int $managementId) + { + try { + return DB::table('park') + ->where('park_id', $parkId) + ->where('management_id', $managementId) + ->first(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場情報取得エラー park_id=" . $parkId . ", error=" . $e->getMessage()); + return null; + } + } + + /** + * zone情報取得 + * + * @param int $parkId + * @return \Illuminate\Support\Collection + */ + private function getZones(int $parkId) + { + try { + return DB::table('zone') + ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') + ->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') + ->select( + 'zone.*', + 'psection.psection_subject', + 'ptype.ptype_subject' + ) + ->where('zone.park_id', $parkId) + ->get(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " zone情報取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * reserve情報取得 + * + * @param int $parkId + * @return \Illuminate\Support\Collection + */ + private function getReserves(int $parkId) + { + try { + return DB::table('reserve') + ->join('zone', function ($join) { + $join->on('reserve.psection_id', '=', 'zone.psection_id') + ->on('reserve.ptype_id', '=', 'zone.ptype_id'); + }) + ->join('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') + ->where('reserve.park_id', $parkId) + ->where('reserve.valid_flag', 1) + ->select('reserve.*', 'ptype.ptype_subject') + ->get(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " reserve情報取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 車種別の基準台数集計 + * + * なぜ: 駐輪場の車種別定員を把握するため + * + * @param \Illuminate\Support\Collection $zones + * @return array + */ + private function calculateZoneStandardSum($zones) + { $zoneStandardSum = []; + foreach ($zones as $zone) { $key = $zone->psection_subject; // 「自転車」「原付」など if (!isset($zoneStandardSum[$key])) { @@ -39,14 +169,28 @@ class ParkDetailController extends Controller $zoneStandardSum[$key] += $zone->zone_standard; } - // 空き台数集計用配列 + return $zoneStandardSum; + } + + /** + * 車種別の空き台数計算 + * + * なぜ: reserve(予約)を考慮した実際の空き台数を算出するため + * + * @param \Illuminate\Support\Collection $zones + * @param \Illuminate\Support\Collection $reserves + * @return array + */ + private function calculateVacancy($zones, $reserves) + { $vacancyData = []; + + // zone_tolerance - zone_number を集計 foreach ($zones as $zone) { $key = $zone->psection_id . '_' . $zone->ptype_subject; if (!isset($vacancyData[$key])) { $vacancyData[$key] = 0; } - // zone_tolerance - zone_number を合計 $vacancyData[$key] += ($zone->zone_tolerance - $zone->zone_number); } @@ -54,23 +198,38 @@ class ParkDetailController extends Controller foreach ($reserves as $reserve) { $key = $reserve->psection_id . '_' . $reserve->ptype_subject; if (isset($vacancyData[$key])) { - $vacancyData[$key] -= 1; // 1件分減算 + $vacancyData[$key] -= 1; } } - // 更新期間取得 - $city_grace_periods = DB::table('city') - ->select('city_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time') - ->whereIn('city_id', function ($query) { - $query->select('city_id')->from('park'); - }) - ->get() - ->keyBy('city_id'); + return $vacancyData; + } - // 必要なら他テーブルJOINや追加情報も取得 - return response()->json([ - 'html' => view('regular_contract.park_detail', compact('park', 'zones', 'reserves', 'zoneStandardSum', 'vacancyData', 'city_grace_periods'))->render() - ]); + /** + * 駐輪場の更新可能期間取得 + * + * なぜ: 更新可能期間がcityテーブルからparkテーブルに変更されたため + * + * @param int $parkId + * @return object|null + */ + private function getParkGracePeriod(int $parkId) + { + try { + return DB::table('park') + ->select( + 'park_id', + 'update_grace_period_start_date', + 'update_grace_period_start_time', + 'update_grace_period_end_date', + 'update_grace_period_end_time' + ) + ->where('park_id', $parkId) + ->first(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場更新可能期間取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); + return null; + } } public function showWait($reserve_id) diff --git a/app/Http/Controllers/RegularContractCreateController.php b/app/Http/Controllers/RegularContractCreateController.php index a28b05b..24ca40f 100644 --- a/app/Http/Controllers/RegularContractCreateController.php +++ b/app/Http/Controllers/RegularContractCreateController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Traits\AuthenticatesUser; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Session; @@ -16,150 +17,440 @@ use Carbon\Carbon; class RegularContractCreateController extends Controller { - // 新規作成画面表示 + use AuthenticatesUser; + + /** + * 駐輪場選択画面を表示 + * + * @param Request $request + * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse + */ public function show(Request $request) { - $user_id = session('user_id'); - if (!$user_id) { - return redirect('/login'); + if ($redirect = $this->handleSessionExpired('駐輪場選択')) { + return $redirect; } - $user = DB::table('user')->where('user_id', $user_id)->first(); - // 市町村名(park→city JOINで重複排除) - $cities = DB::table('park') - ->join('city', 'park.city_id', '=', 'city.city_id') - ->select('city.city_id', 'city.city_name') - ->distinct() - ->get(); + $userId = session('user_id'); + $user = DB::table('user')->where('user_id', $userId)->first(); - // city_idごとの更新可能期間情報を取得 - $city_grace_periods = DB::table('city') - ->select('city_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time') - ->get() - ->keyBy('city_id'); + // マルチテナント対応のため、運営元情報をセッションから取得 + $management = session('management'); + if (!$management) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 運営元情報が見つからない user_id=" . $userId); + abort(404); + } - // 駅名(stationテーブルのstation_neighbor_station全件) - $stations = DB::table('station') - ->select('station_neighbor_station') - ->distinct() - ->get(); - - // 駐輪場名(parkテーブルのpark_name全件) - $parks = DB::table('park') - ->select('park_id', 'park_name') - ->distinct() - ->get(); - - // テーブル表示用データ(park/city/station JOIN, park_id昇順, 10件ずつページング) - $page = request()->input('page', 1); + // 検索条件・ページング取得 + $cityId = $request->input('city_id'); + $stationName = $request->input('station_neighbor_station'); + $parkId = $request->input('park_id'); + $page = (int)$request->input('page', 1); $perPage = 10; - $city_id = request()->input('city_id'); - $station_name = request()->input('station_neighbor_station'); - $park_id = request()->input('park_id'); - $query = DB::table('park') - ->join('city', 'park.city_id', '=', 'city.city_id') - ->leftJoin('station', 'park.park_id', '=', 'station.park_id') - ->select( - 'park.park_id', - 'park.park_name', - 'park.park_ruby', - 'city.city_name', - 'city.city_id', - 'station.station_neighbor_station', - 'station.station_name_ruby' - ); - - if ($city_id) { - $query->where('city.city_id', $city_id); - } - if ($station_name) { - $query->where('station.station_neighbor_station', $station_name); - } - if ($park_id) { - $query->where('park.park_id', $park_id); - } - - // 並び替えパラメータ取得 - $sort = request()->input('sort', 'park_id'); - $order = request()->input('order', 'asc'); - $sortable = [ - 'park_ruby' => 'park.park_ruby', - 'city_id' => 'city.city_id', - 'station_name_ruby' => 'station.station_name_ruby', - 'park_id' => 'park.park_id', - ]; - $query->orderBy('park.park_id', 'asc'); - - $total = $query->count(); - $parks_table = $query->get(); - - if ($sort === 'park_ruby' || $sort === 'station_name_ruby') { - $collator = new \Collator('ja'); - $parks_table = $parks_table->sort(function ($a, $b) use ($order, $sort, $collator) { - $a_val = $a->$sort ?? ''; - $b_val = $b->$sort ?? ''; - return $order === 'asc' - ? $collator->compare($a_val, $b_val) - : $collator->compare($b_val, $a_val); - })->values(); - } else { - // park_id, city_idなどはSQLのorderByで十分なので、ここでPHPソートは不要 - if (isset($sortable[$sort])) { - $parks_table = $parks_table->sortBy($sort, SORT_REGULAR, $order === 'desc')->values(); - } - } - $page = request()->input('page', 1); - $perPage = 10; - $parks_table = $parks_table->slice(($page - 1) * $perPage, $perPage)->values(); - - // zoneテーブルデータを取得(psectionテーブルとJOINしてpsection_subjectも取得) - $zones = DB::table('zone') - ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') - ->select('zone.zone_id', 'zone.park_id', 'zone.ptype_id', 'zone.psection_id', 'zone.zone_number', 'zone.zone_tolerance', 'psection.psection_subject') - ->get() - ->groupBy('park_id'); - - // 空き予約マスタデータを取得 - $reserve = DB::table('reserve') - ->select('reserve_id', 'park_id', 'ptype_id', 'psection_id') - ->where('valid_flag', 1) - ->get() - ->groupBy('park_id'); - - // ルート名で画面表示を切り替え(新規定期契約 or 駐輪場検索) + // ルート名で画面種別判定(新規定期契約 or 駐輪場検索) $isRegularContract = $request->route()->getName() === 'regular_contract.create'; + $activeMenu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1'; - // ヘッダーの選択状態を分岐 - $active_menu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1'; + // マスタデータ取得(運営元フィルタ適用) + $cities = $this->getCitiesList($management->management_id); + $stations = $this->getStationsList($management->management_id); + $parks = $this->getParksList($management->management_id); - if ($isRegularContract) { - \Log::info('新規定期契約-駐輪場選択画面にアクセス', [ - 'user_id' => $user_id, - ]); - } else { - \Log::info('駐輪場検索-駐輪場選択画面にアクセス', [ - 'user_id' => $user_id, - ]); - } + // 運営元が取り扱う車種一覧を取得 + $availableVehicles = $this->getAvailableVehicles($management->management_id); + + // 駐輪場テーブルデータ取得(検索・ページング、運営元フィルタ適用) + $parksTableData = $this->getParksTableData($management->management_id, $cityId, $stationName, $parkId, $page, $perPage); + + // zone・reserve マスタ取得(運営元フィルタ適用) + $zones = $this->getZonesByParkId($management->management_id); + $reserve = $this->getReserveByParkId($management->management_id); + + // 駐輪場ごとの更新可能期間取得(運営元フィルタ適用) + $parkGracePeriods = $this->getParkGracePeriods($management->management_id); + + // 駐輪場ごと・車種ごとの状態を事前計算 + $parksTableData['data'] = $this->calculateVehicleStatus( + $parksTableData['data'], + $zones, + $reserve, + $parkGracePeriods, + $availableVehicles + ); + + // アクセスログ記録 + $screenName = $isRegularContract ? '新規定期契約登録' : '駐輪場検索'; + \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " {$screenName}画面アクセス user_id=" . $userId . ", management_id=" . $management->management_id . ", city_id=" . ($cityId ?? 'null') . ", park_id=" . ($parkId ?? 'null')); return view('regular_contract.create', [ - 'active_menu' => $active_menu, // この画面のID - 'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用) + 'active_menu' => $activeMenu, + 'user_name' => $user->user_name ?? '', 'cities' => $cities, 'stations' => $stations, 'parks' => $parks, - 'parks_table' => $parks_table, - 'parks_table_total' => $total, + 'parks_table' => $parksTableData['data'], + 'parks_table_total' => $parksTableData['total'], 'parks_table_page' => $page, 'parks_table_perPage' => $perPage, - 'zones' => $zones, - 'city_grace_periods' => $city_grace_periods, + 'available_vehicles' => $availableVehicles, 'reserve' => $reserve, - 'isRegularContract' => $isRegularContract + 'isRegularContract' => $isRegularContract, ]); } + /** + * 市町村一覧取得(運営元フィルタ適用) + * マルチテナント対応のため、運営元に紐づく駐輪場の市町村のみ表示 + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getCitiesList(int $managementId) + { + try { + return DB::table('park') + ->join('city', 'park.city_id', '=', 'city.city_id') + ->where('park.management_id', $managementId) + ->select('city.city_id', 'city.city_name') + ->distinct() + ->get(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 市町村一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 駅名一覧取得(運営元フィルタ適用) + * + * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場の駅のみ表示 + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getStationsList(int $managementId) + { + try { + return DB::table('station') + ->join('park', 'station.park_id', '=', 'park.park_id') + ->where('park.management_id', $managementId) + ->select('station.station_neighbor_station') + ->distinct() + ->get(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駅名一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 駐輪場名一覧取得(運営元フィルタ適用) + * + * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のみ表示 + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getParksList(int $managementId) + { + try { + return DB::table('park') + ->where('management_id', $managementId) + ->select('park_id', 'park_name') + ->distinct() + ->get(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場名一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 駐輪場テーブルデータ取得(検索・ページング適用、運営元フィルタ適用) + * + * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のみ表示 + * + * @param int $managementId + * @param int|null $cityId + * @param string|null $stationName + * @param int|null $parkId + * @param int $page + * @param int $perPage + * @return array ['data' => Collection, 'total' => int] + */ + private function getParksTableData(int $managementId, $cityId, $stationName, $parkId, int $page, int $perPage) + { + try { + $query = DB::table('park') + ->join('city', 'park.city_id', '=', 'city.city_id') + ->leftJoin('station', 'park.park_id', '=', 'station.park_id') + ->where('park.management_id', $managementId) + ->select( + 'park.park_id', + 'park.park_name', + 'park.park_ruby', + 'city.city_name', + 'city.city_id', + 'station.station_neighbor_station', + 'station.station_name_ruby' + ); + + // 検索条件適用 + if ($cityId) { + $query->where('city.city_id', $cityId); + } + if ($stationName) { + $query->where('station.station_neighbor_station', $stationName); + } + if ($parkId) { + $query->where('park.park_id', $parkId); + } + + $query->orderBy('park.park_id', 'asc'); + $total = $query->count(); + + // ページング適用 + $data = $query + ->skip(($page - 1) * $perPage) + ->take($perPage) + ->get(); + + \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " 駐輪場データ取得成功 management_id=" . $managementId . ", 件数=" . $total . ", page=" . $page); + + return [ + 'data' => $data, + 'total' => $total, + ]; + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場テーブルデータ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return [ + 'data' => collect(), + 'total' => 0, + ]; + } + } + + /** + * zone データを park_id でグループ化して取得(運営元フィルタ適用) + * + * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のzoneのみ取得 + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getZonesByParkId(int $managementId) + { + try { + return DB::table('zone') + ->join('park', 'zone.park_id', '=', 'park.park_id') + ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') + ->where('park.management_id', $managementId) + ->select( + 'zone.zone_id', + 'zone.park_id', + 'zone.ptype_id', + 'zone.psection_id', + 'zone.zone_number', + 'zone.zone_tolerance', + 'psection.psection_subject' + ) + ->get() + ->groupBy('park_id'); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " zone データ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * reserve データを park_id でグループ化して取得(運営元フィルタ適用) + * + * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のreserveのみ取得 + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getReserveByParkId(int $managementId) + { + try { + return DB::table('reserve') + ->join('park', 'reserve.park_id', '=', 'park.park_id') + ->where('park.management_id', $managementId) + ->where('reserve.valid_flag', 1) + ->select('reserve.reserve_id', 'reserve.park_id', 'reserve.ptype_id', 'reserve.psection_id') + ->get() + ->groupBy('park_id'); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " reserve データ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 駐輪場ごとの更新可能期間を取得(運営元フィルタ適用) + * + * なぜ: 更新可能期間がcityテーブルからparkテーブルに変更されたため + * + * @param int $managementId + * @return \Illuminate\Support\Collection + */ + private function getParkGracePeriods(int $managementId) + { + try { + return DB::table('park') + ->where('management_id', $managementId) + ->select( + 'park_id', + 'update_grace_period_start_date', + 'update_grace_period_start_time', + 'update_grace_period_end_date', + 'update_grace_period_end_time' + ) + ->get() + ->keyBy('park_id'); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場更新可能期間取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return collect(); + } + } + + /** + * 運営元が取り扱う車種一覧を取得 + * + * なぜ: マルチテナント対応のため、運営元ごとに表示する車種を可変にする + * + * @param int $managementId + * @return array + */ + private function getAvailableVehicles(int $managementId) + { + try { + return DB::table('zone') + ->join('park', 'zone.park_id', '=', 'park.park_id') + ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') + ->where('park.management_id', $managementId) + ->whereNotNull('psection.psection_subject') + ->select('psection.psection_subject') + ->distinct() + ->orderByRaw("FIELD(psection.psection_subject, '自転車', '原付', '自動二輪', '自動車')") + ->pluck('psection_subject') + ->toArray(); + } catch (\Exception $e) { + \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 取り扱い車種取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage()); + return []; + } + } + + /** + * 駐輪場ごと・車種ごとの状態を計算 + * + * なぜ: Bladeでのビジネスロジック実行を避け、Fat Controller回避のため事前計算 + * + * @param \Illuminate\Support\Collection $parks + * @param \Illuminate\Support\Collection $zones + * @param \Illuminate\Support\Collection $reserve + * @param \Illuminate\Support\Collection $parkGracePeriods + * @param array $availableVehicles + * @return \Illuminate\Support\Collection + */ + private function calculateVehicleStatus($parks, $zones, $reserve, $parkGracePeriods, $availableVehicles) + { + $now = \Carbon\Carbon::now(); + + foreach ($parks as $park) { + $park->vehicle_status = []; + + foreach ($availableVehicles as $vehicle) { + $zonesForType = ($zones[$park->park_id] ?? collect())->where('psection_subject', $vehicle); + + // 空き台数計算 + $hasVacancy = false; + foreach ($zonesForType as $zone) { + $reserveCount = ($reserve[$park->park_id] ?? collect()) + ->where('psection_id', $zone->psection_id) + ->where('ptype_id', $zone->ptype_id) + ->count(); + $vacancy = $zone->zone_tolerance - $zone->zone_number - $reserveCount; + if ($vacancy > 0) { + $hasVacancy = true; + break; + } + } + + // 猶予期間判定 + $grace = $parkGracePeriods[$park->park_id] ?? null; + $inGrace = $this->isInGracePeriod($grace, $now); + + // 状態判定 + if ($zonesForType->isEmpty() || !$grace) { + $park->vehicle_status[$vehicle] = 'none'; + } elseif ($inGrace && $hasVacancy) { + $park->vehicle_status[$vehicle] = 'available'; + } elseif (!$inGrace) { + $park->vehicle_status[$vehicle] = 'out_of_period'; + } elseif (!$hasVacancy) { + $park->vehicle_status[$vehicle] = 'waiting'; + } else { + $park->vehicle_status[$vehicle] = 'none'; + } + } + } + + return $parks; + } + + /** + * 猶予期間内かどうかを判定 + * + * なぜ: 複雑な日時判定ロジックを分離し、可読性向上 + * + * @param object|null $grace + * @param \Carbon\Carbon $now + * @return bool + */ + private function isInGracePeriod($grace, $now) + { + if (!$grace) { + return false; + } + + // バリデーション + if ( + !is_numeric($grace->update_grace_period_start_date) || + !preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_start_time) || + !is_numeric($grace->update_grace_period_end_date) || + !preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_end_time) + ) { + return false; + } + + $year = $now->year; + $month = $now->month; + $startDay = (int)$grace->update_grace_period_start_date; + $endDay = (int)$grace->update_grace_period_end_date; + + if ($startDay > $endDay) { + // 月またぎ(例: 25日~5日) + $prevMonth = $now->copy()->subMonth(); + $startPrev = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $prevMonth->year, $prevMonth->month, $startDay, $grace->update_grace_period_start_time)); + $endCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time)); + + $startCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time)); + $nextMonth = $month == 12 ? 1 : $month + 1; + $nextYear = $month == 12 ? $year + 1 : $year; + $endNext = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $nextYear, $nextMonth, $endDay, $grace->update_grace_period_end_time)); + + return $now->between($startPrev, $endCurr) || $now->between($startCurr, $endNext); + } else { + // 同月(例: 5日~25日) + $start = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time)); + $end = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time)); + + return $now->between($start, $end); + } + } + public function regulationCheck(Request $request) { // GETパラメータを取得 diff --git a/app/Http/Traits/AuthenticatesUser.php b/app/Http/Traits/AuthenticatesUser.php index 5751db5..054396e 100644 --- a/app/Http/Traits/AuthenticatesUser.php +++ b/app/Http/Traits/AuthenticatesUser.php @@ -38,11 +38,11 @@ trait AuthenticatesUser if ($saveIntended) { // ログイン後に元の画面に戻る - return redirect()->guest(route('swo8_1', ['management_code' => $managementCode])); + return redirect()->guest(route('login_input', ['management_code' => $managementCode])); } else { // ログイン後はマイページトップへ session()->forget('url.intended'); - return redirect()->route('swo8_1', ['management_code' => $managementCode]); + return redirect()->route('login_input', ['management_code' => $managementCode]); } } } diff --git a/public/assets/css/mypage/tablesorter-blue.css b/public/assets/css/mypage/tablesorter-blue.css index 1290f8d..9b41940 100644 --- a/public/assets/css/mypage/tablesorter-blue.css +++ b/public/assets/css/mypage/tablesorter-blue.css @@ -17,7 +17,6 @@ table.tablesorter tfoot tr th { } table.tablesorter thead tr .header { - background-image: url(bg.gif); background-repeat: no-repeat; background-position: center right; cursor: pointer; @@ -34,14 +33,6 @@ table.tablesorter tbody tr.odd td { background-color: #F0F0F6; } -table.tablesorter thead tr .headerSortUp { - background-image: url(asc.gif); -} - -table.tablesorter thead tr .headerSortDown { - background-image: url(desc.gif); -} - table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp { background-color: #8dbdd8; diff --git a/public/assets/js/commons.js b/public/assets/js/commons.js index 97c3d3e..8e98edb 100644 --- a/public/assets/js/commons.js +++ b/public/assets/js/commons.js @@ -115,17 +115,26 @@ $(function () { }); // 駐輪場詳細ポップアップ -$(document).on('click', '.btn-popup', function () { - var parkId = $(this).data('park-id'); +$(document).on('click', '.btn-popup', function (e) { + e.preventDefault(); + const url = $(this).data('url'); + + // data-url が存在しない場合はエラー + if (!url) { + console.error('data-url属性が設定されていません'); + alert('URLが取得できませんでした。'); + return; + } + $.ajax({ - url: '/api/park-detail/' + parkId, + url: url, method: 'GET', - success: function (data) { - $('#modalArea').html(data.html); // モーダル全体を挿入 - $('#parkDetailModal').modal('show'); // モーダルを表示 + success: function (response) { + $('#modalArea').html(response.html); + $('#popup-modal').modal('show'); }, error: function () { - alert('詳細情報の取得に失敗しました'); + alert('詳細情報の取得に失敗しました。'); } }); }); diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index de412e7..b296d2e 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -10,7 +10,7 @@ - + @@ -18,6 +18,7 @@ + @yield('head')