diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index be151d4..618997c 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -5,9 +5,11 @@ namespace App\Http\Controllers; use App\Ejaculation; use App\User; use Carbon\Carbon; +use Carbon\CarbonInterface; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use Validator; class UserController extends Controller { @@ -60,7 +62,18 @@ SQL ->limit(10) ->get(); - return view('user.profile')->with(compact('user', 'ejaculations', 'tags')); + // シコ草 + $countByDayQuery = $this->countEjaculationByDay($user) + ->where('ejaculated_date', '>=', now()->startOfMonth()->subMonths(9)) + ->where('ejaculated_date', '<', now()->addMonth()->startOfMonth()) + ->get(); + $countByDay = []; + foreach ($countByDayQuery as $data) { + $date = Carbon::createFromFormat('Y/m/d', $data->date); + $countByDay[$date->timestamp] = $data->count; + } + + return view('user.profile')->with(compact('user', 'ejaculations', 'tags', 'countByDay')); } public function stats($name) @@ -70,73 +83,72 @@ SQL abort(404); } - $dateUntil = now()->addMonth()->startOfMonth(); + $availableMonths = $this->makeStatsAvailableMonths($user); + $graphData = $this->makeGraphData($user); - $groupByDay = Ejaculation::select(DB::raw( - <<<'SQL' -to_char(ejaculated_date, 'YYYY/MM/DD') AS "date", -count(*) AS "count" -SQL - )) - ->where('user_id', $user->id) - ->where('ejaculated_date', '<', $dateUntil) - ->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")) - ->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")) - ->get(); + return view('user.stats.index')->with(compact('user', 'graphData', 'availableMonths')); + } - $groupByHour = Ejaculation::select(DB::raw( - <<<'SQL' -to_char(ejaculated_date, 'HH24') AS "hour", -count(*) AS "count" -SQL - )) - ->where('user_id', $user->id) - ->where('ejaculated_date', '<', $dateUntil) - ->groupBy(DB::raw("to_char(ejaculated_date, 'HH24')")) - ->orderBy(DB::raw('1')) - ->get(); - - $dailySum = []; - $monthlySum = []; - $yearlySum = []; - $dowSum = array_fill(0, 7, 0); - $hourlySum = array_fill(0, 24, 0); - - // 年間グラフ用の配列初期化 - if ($groupByDay->first() !== null) { - $year = Carbon::createFromFormat('Y/m/d', $groupByDay->first()->date)->year; - $currentYear = date('Y'); - for (; $year <= $currentYear; $year++) { - $yearlySum[$year] = 0; - } + public function statsYearly($name, $year) + { + $user = User::where('name', $name)->first(); + if (empty($user)) { + abort(404); } - foreach ($groupByDay as $data) { - $date = Carbon::createFromFormat('Y/m/d', $data->date); - $yearAndMonth = $date->format('Y/m'); - - $dailySum[$date->timestamp] = $data->count; - $yearlySum[$date->year] += $data->count; - $dowSum[$date->dayOfWeek] += $data->count; - $monthlySum[$yearAndMonth] = ($monthlySum[$yearAndMonth] ?? 0) + $data->count; + $validator = Validator::make(compact('year'), [ + 'year' => 'required|date_format:Y' + ]); + if ($validator->fails()) { + return redirect()->route('user.stats', compact('name')); } - foreach ($groupByHour as $data) { - $hour = (int)$data->hour; - $hourlySum[$hour] += $data->count; + $availableMonths = $this->makeStatsAvailableMonths($user); + if (!isset($availableMonths[$year])) { + return redirect()->route('user.stats', compact('name')); } - $graphData = [ - 'dailySum' => $dailySum, - 'dowSum' => $dowSum, - 'monthlySum' => $monthlySum, - 'yearlyKey' => array_keys($yearlySum), - 'yearlySum' => array_values($yearlySum), - 'hourlyKey' => array_keys($hourlySum), - 'hourlySum' => array_values($hourlySum), - ]; + $graphData = $this->makeGraphData( + $user, + Carbon::createFromDate($year, 1, 1, config('app.timezone'))->startOfDay(), + Carbon::createFromDate($year, 1, 1, config('app.timezone'))->addYear()->startOfDay() + ); - return view('user.stats')->with(compact('user', 'graphData')); + return view('user.stats.yearly') + ->with(compact('user', 'graphData', 'availableMonths')) + ->with('currentYear', (int) $year); + } + + public function statsMonthly($name, $year, $month) + { + $user = User::where('name', $name)->first(); + if (empty($user)) { + abort(404); + } + + $validator = Validator::make(compact('year', 'month'), [ + 'year' => 'required|date_format:Y', + 'month' => 'required|date_format:m' + ]); + if ($validator->fails()) { + return redirect()->route('user.stats.yearly', compact('name', 'year')); + } + + $availableMonths = $this->makeStatsAvailableMonths($user); + if (!isset($availableMonths[$year]) || !in_array($month, $availableMonths[$year], false)) { + return redirect()->route('user.stats.yearly', compact('name', 'year')); + } + + $graphData = $this->makeGraphData( + $user, + Carbon::createFromDate($year, $month, 1, config('app.timezone'))->startOfDay(), + Carbon::createFromDate($year, $month, 1, config('app.timezone'))->addMonth()->startOfDay() + ); + + return view('user.stats.monthly') + ->with(compact('user', 'graphData', 'availableMonths')) + ->with('currentYear', (int) $year) + ->with('currentMonth', (int) $month); } public function okazu($name) @@ -191,4 +203,104 @@ SQL return view('user.likes')->with(compact('user', 'likes')); } + + private function makeStatsAvailableMonths(User $user): array + { + $availableMonths = []; + $oldest = $user->ejaculations()->orderBy('ejaculated_date')->first(); + if (isset($oldest)) { + $oldestMonth = $oldest->ejaculated_date->startOfMonth(); + $currentMonth = now()->startOfMonth(); + for ($month = $currentMonth; $oldestMonth <= $currentMonth; $month = $month->subMonth()) { + if (!isset($availableMonths[$month->year])) { + $availableMonths[$month->year] = []; + } + $availableMonths[$month->year][] = $month->month; + } + } + + return $availableMonths; + } + + private function makeGraphData(User $user, CarbonInterface $dateSince = null, CarbonInterface $dateUntil = null): array + { + if ($dateUntil === null) { + $dateUntil = now()->addMonth()->startOfMonth(); + } + $dateCondition = [ + ['ejaculated_date', '<', $dateUntil], + ]; + if ($dateSince !== null) { + $dateCondition[] = ['ejaculated_date', '>=', $dateSince]; + } + + $groupByDay = $this->countEjaculationByDay($user) + ->where($dateCondition) + ->get(); + + $groupByHour = Ejaculation::select(DB::raw( + <<<'SQL' +to_char(ejaculated_date, 'HH24') AS "hour", +count(*) AS "count" +SQL + )) + ->where('user_id', $user->id) + ->where($dateCondition) + ->groupBy(DB::raw("to_char(ejaculated_date, 'HH24')")) + ->orderBy(DB::raw('1')) + ->get(); + + $dailySum = []; + $monthlySum = []; + $yearlySum = []; + $dowSum = array_fill(0, 7, 0); + $hourlySum = array_fill(0, 24, 0); + + // 年間グラフ用の配列初期化 + if ($groupByDay->first() !== null) { + $year = Carbon::createFromFormat('Y/m/d', $groupByDay->first()->date)->year; + $currentYear = date('Y'); + for (; $year <= $currentYear; $year++) { + $yearlySum[$year] = 0; + } + } + + foreach ($groupByDay as $data) { + $date = Carbon::createFromFormat('Y/m/d', $data->date); + $yearAndMonth = $date->format('Y/m'); + + $dailySum[$date->timestamp] = $data->count; + $yearlySum[$date->year] += $data->count; + $dowSum[$date->dayOfWeek] += $data->count; + $monthlySum[$yearAndMonth] = ($monthlySum[$yearAndMonth] ?? 0) + $data->count; + } + + foreach ($groupByHour as $data) { + $hour = (int)$data->hour; + $hourlySum[$hour] += $data->count; + } + + return [ + 'dailySum' => $dailySum, + 'dowSum' => $dowSum, + 'monthlySum' => $monthlySum, + 'yearlyKey' => array_keys($yearlySum), + 'yearlySum' => array_values($yearlySum), + 'hourlyKey' => array_keys($hourlySum), + 'hourlySum' => array_values($hourlySum), + ]; + } + + private function countEjaculationByDay(User $user) + { + return Ejaculation::select(DB::raw( + <<<'SQL' +to_char(ejaculated_date, 'YYYY/MM/DD') AS "date", +count(*) AS "count" +SQL + )) + ->where('user_id', $user->id) + ->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")) + ->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')")); + } } diff --git a/resources/assets/js/user/profile.js b/resources/assets/js/user/profile.js new file mode 100644 index 0000000..24cba77 --- /dev/null +++ b/resources/assets/js/user/profile.js @@ -0,0 +1,15 @@ +import CalHeatMap from 'cal-heatmap'; + +if (document.getElementById('cal-heatmap')) { + new CalHeatMap().init({ + itemSelector: '#cal-heatmap', + domain: 'month', + subDomain: 'day', + domainLabelFormat: '%Y/%m', + weekStartOnMonday: false, + start: new Date().setMonth(new Date().getMonth() - 9), + range: 10, + data: JSON.parse(document.getElementById('count-by-day').textContent), + legend: [1, 2, 3, 4] + }); +} diff --git a/resources/assets/js/user/stats.js b/resources/assets/js/user/stats.js index 2bc1c7c..d1fda72 100644 --- a/resources/assets/js/user/stats.js +++ b/resources/assets/js/user/stats.js @@ -1,6 +1,6 @@ import CalHeatMap from 'cal-heatmap'; import Chart from 'chart.js'; -import {addMonths, format, startOfMonth, subMonths} from 'date-fns'; +import {addMonths, format} from 'date-fns'; const graphData = JSON.parse(document.getElementById('graph-data').textContent); @@ -90,53 +90,35 @@ function createMonthlyGraphData(from) { return {keys, values}; } -new CalHeatMap().init({ - itemSelector: '#cal-heatmap', - domain: 'month', - subDomain: 'day', - domainLabelFormat: '%Y/%m', - weekStartOnMonday: false, - start: new Date().setMonth(new Date().getMonth() - 9), - range: 10, - data: graphData.dailySum, - legend: [1, 2, 3, 4] -}); - -// 直近1年の月間グラフのデータを準備 -const monthlyTermFrom = subMonths(startOfMonth(new Date()), 11); -const {keys: monthlyKey, values: monthlySum} = createMonthlyGraphData(monthlyTermFrom); - -const monthlyGraph = createLineGraph('monthly-graph', monthlyKey, monthlySum); -createLineGraph('yearly-graph', graphData.yearlyKey, graphData.yearlySum); -createBarGraph('hourly-graph', graphData.hourlyKey, graphData.hourlySum); -createBarGraph('dow-graph', ['日', '月', '火', '水', '木', '金', '土'], graphData.dowSum); - -// 月間グラフの期間セレクターを準備 -const monthlyTermSelector = document.getElementById('monthly-term'); -const earliestYear = [...new Set(Object.keys(graphData.monthlySum).map(v => v.substr(0, 4)))].shift(); -for (let year = earliestYear; year <= new Date().getFullYear(); year++) { - const opt = document.createElement('option'); - opt.setAttribute('value', year); - opt.textContent = `${year}年`; - monthlyTermSelector.insertBefore(opt, monthlyTermSelector.firstChild); -} -if (monthlyTermSelector.children.length) { - monthlyTermSelector.selectedIndex = 0; +function getCurrentYear() { + const year = location.pathname.split('/').pop(); + return /^(20[0-9]{2})$/.test(year) ? year : null; } -monthlyTermSelector.addEventListener('change', function (e) { - let monthlyTermFrom; - if (e.target.selectedIndex === 0) { - // 今年のデータを表示する時は、直近12ヶ月を表示 - monthlyTermFrom = subMonths(startOfMonth(new Date()), 11); - } else { - // 過去のデータを表示する時は、選択年の1〜12月を表示 - monthlyTermFrom = new Date(e.target.value, 0, 1); - } +if (document.getElementById('cal-heatmap')) { + new CalHeatMap().init({ + itemSelector: '#cal-heatmap', + domain: 'month', + subDomain: 'day', + domainLabelFormat: '%Y/%m', + weekStartOnMonday: false, + start: new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0), + range: 12, + data: graphData.dailySum, + legend: [1, 2, 3, 4] + }); +} - const {keys, values} = createMonthlyGraphData(monthlyTermFrom); - - monthlyGraph.data.labels = keys; - monthlyGraph.data.datasets[0].data = values; - monthlyGraph.update(); -}); +if (document.getElementById('monthly-graph')) { + const {keys: monthlyKey, values: monthlySum} = createMonthlyGraphData(new Date(getCurrentYear(), 0, 1, 0, 0, 0, 0)); + createLineGraph('monthly-graph', monthlyKey, monthlySum); +} +if (document.getElementById('yearly-graph')) { + createLineGraph('yearly-graph', graphData.yearlyKey, graphData.yearlySum); +} +if (document.getElementById('hourly-graph')) { + createBarGraph('hourly-graph', graphData.hourlyKey, graphData.hourlySum); +} +if (document.getElementById('dow-graph')) { + createBarGraph('dow-graph', ['日', '月', '火', '水', '木', '金', '土'], graphData.dowSum); +} diff --git a/resources/views/components/profile-mini.blade.php b/resources/views/components/profile-mini.blade.php new file mode 100644 index 0000000..7f4dd97 --- /dev/null +++ b/resources/views/components/profile-mini.blade.php @@ -0,0 +1,14 @@ +
+ +
+
+ {{ $user->display_name }} +
+
+ @{{ $user->name }} + @if ($user->is_protected) + + @endif +
+
+
diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 0555b05..5295d28 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -9,20 +9,8 @@
-
- -
-
- {{ Auth::user()->display_name }} -
-
- @{{ Auth::user()->name }} - @if (Auth::user()->is_protected) - - @endif -
-
-
+ @component('components.profile-mini', ['user' => Auth::user(), 'class' => 'mb-4']) + @endcomponent @component('components.profile-stats', ['user' => Auth::user()]) @endcomponent
diff --git a/resources/views/user/base.blade.php b/resources/views/user/base.blade.php index 49598ac..67c49ee 100644 --- a/resources/views/user/base.blade.php +++ b/resources/views/user/base.blade.php @@ -4,8 +4,17 @@
- @component('components.profile', ['user' => $user]) - @endcomponent + @if (Route::currentRouteName() === 'user.profile') + @component('components.profile', ['user' => $user]) + @endcomponent + @else +
+
+ @component('components.profile-mini', ['user' => $user]) + @endcomponent +
+
+ @endif @section('sidebar') @show
@@ -15,7 +24,7 @@ タイムライン