feat: [dashboard] 주간 날씨 위젯 추가
- 기상청 공공데이터포털 API 연동 (단기+중기 7일 예보) - WeatherService: 3시간 캐시, SKY/PTY 아이콘 매핑 - HTMX 비동기 로딩 + 스켈레톤 UI - 오늘 카드 파란색 강조, 요일/날짜/아이콘/기온 표시
This commit is contained in:
15
app/Http/Controllers/DashboardWeatherController.php
Normal file
15
app/Http/Controllers/DashboardWeatherController.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\WeatherService;
|
||||
|
||||
class DashboardWeatherController extends Controller
|
||||
{
|
||||
public function weather(WeatherService $weatherService)
|
||||
{
|
||||
$forecasts = $weatherService->getWeeklyForecast();
|
||||
|
||||
return view('dashboard.partials.weather', compact('forecasts'));
|
||||
}
|
||||
}
|
||||
280
app/Services/WeatherService.php
Normal file
280
app/Services/WeatherService.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class WeatherService
|
||||
{
|
||||
private const BASE_SHORT = 'https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst';
|
||||
|
||||
private const BASE_MID_TA = 'https://apis.data.go.kr/1360000/MidFcstInfoService/getMidTa';
|
||||
|
||||
private const BASE_MID_LAND = 'https://apis.data.go.kr/1360000/MidFcstInfoService/getMidLandFcst';
|
||||
|
||||
// 서울 격자 좌표
|
||||
private const NX = 60;
|
||||
|
||||
private const NY = 127;
|
||||
|
||||
// 중기예보 지역 코드 (서울)
|
||||
private const MID_TA_REG_ID = '11B10101'; // 서울 기온
|
||||
|
||||
private const MID_LAND_REG_ID = '11B00000'; // 서울·인천·경기
|
||||
|
||||
private string $serviceKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->serviceKey = config('services.kma.service_key', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 7일 예보 데이터 반환 (3시간 캐시)
|
||||
*/
|
||||
public function getWeeklyForecast(): array
|
||||
{
|
||||
if (empty($this->serviceKey)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Cache::remember('weather_forecast_7days', 10800, function () {
|
||||
try {
|
||||
return $this->buildForecast();
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('WeatherService: 예보 조합 실패', ['error' => $e->getMessage()]);
|
||||
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function buildForecast(): array
|
||||
{
|
||||
$today = Carbon::today();
|
||||
$forecasts = [];
|
||||
|
||||
// 1) 단기예보 (오늘 ~ +2일, 최대 3일)
|
||||
$short = $this->fetchShortForecast();
|
||||
foreach ($short as $date => $data) {
|
||||
$forecasts[$date] = $data;
|
||||
}
|
||||
|
||||
// 2) 중기기온 + 중기날씨 (3일 ~ 7일)
|
||||
$midTa = $this->fetchMidTemperature();
|
||||
$midLand = $this->fetchMidLandForecast();
|
||||
|
||||
for ($i = 3; $i <= 7; $i++) {
|
||||
$date = $today->copy()->addDays($i)->format('Ymd');
|
||||
$forecasts[$date] = [
|
||||
'date' => $date,
|
||||
'tmn' => $midTa["taMin{$i}"] ?? null,
|
||||
'tmx' => $midTa["taMax{$i}"] ?? null,
|
||||
'icon' => $this->midWeatherToIcon($midLand["wf{$i}Am"] ?? ($midLand["wf{$i}"] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
// 날짜순 정렬 후 7일만
|
||||
ksort($forecasts);
|
||||
|
||||
return array_slice(array_values($forecasts), 0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 단기예보 API 호출 — TMN/TMX/SKY/PTY 추출
|
||||
*/
|
||||
private function fetchShortForecast(): array
|
||||
{
|
||||
$now = Carbon::now();
|
||||
// base_date: 오늘, base_time: 0200 (02시 발표 → TMN/TMX 포함)
|
||||
$baseDate = $now->hour < 2 ? $now->copy()->subDay()->format('Ymd') : $now->format('Ymd');
|
||||
$baseTime = '0200';
|
||||
|
||||
$response = Http::timeout(10)->get(self::BASE_SHORT, [
|
||||
'serviceKey' => $this->serviceKey,
|
||||
'pageNo' => 1,
|
||||
'numOfRows' => 1000,
|
||||
'dataType' => 'JSON',
|
||||
'base_date' => $baseDate,
|
||||
'base_time' => $baseTime,
|
||||
'nx' => self::NX,
|
||||
'ny' => self::NY,
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::warning('WeatherService: 단기예보 API 실패', ['status' => $response->status()]);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = $response->json();
|
||||
$items = data_get($body, 'response.body.items.item', []);
|
||||
|
||||
if (empty($items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 날짜별 그룹
|
||||
$grouped = [];
|
||||
foreach ($items as $item) {
|
||||
$fcstDate = $item['fcstDate'];
|
||||
$category = $item['category'];
|
||||
$value = $item['fcstValue'];
|
||||
|
||||
if (! isset($grouped[$fcstDate])) {
|
||||
$grouped[$fcstDate] = ['date' => $fcstDate, 'tmn' => null, 'tmx' => null, 'sky' => null, 'pty' => null];
|
||||
}
|
||||
|
||||
match ($category) {
|
||||
'TMN' => $grouped[$fcstDate]['tmn'] = (int) $value,
|
||||
'TMX' => $grouped[$fcstDate]['tmx'] = (int) $value,
|
||||
'SKY' => $grouped[$fcstDate]['sky'] ??= (int) $value,
|
||||
'PTY' => $grouped[$fcstDate]['pty'] ??= (int) $value,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
// 아이콘 매핑
|
||||
$result = [];
|
||||
foreach ($grouped as $date => $data) {
|
||||
$result[$date] = [
|
||||
'date' => $date,
|
||||
'tmn' => $data['tmn'],
|
||||
'tmx' => $data['tmx'],
|
||||
'icon' => $this->shortWeatherToIcon($data['sky'] ?? 1, $data['pty'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 중기기온 API 호출
|
||||
*/
|
||||
private function fetchMidTemperature(): array
|
||||
{
|
||||
$tmFc = $this->getMidBaseTmFc();
|
||||
|
||||
$response = Http::timeout(10)->get(self::BASE_MID_TA, [
|
||||
'serviceKey' => $this->serviceKey,
|
||||
'pageNo' => 1,
|
||||
'numOfRows' => 10,
|
||||
'dataType' => 'JSON',
|
||||
'regId' => self::MID_TA_REG_ID,
|
||||
'tmFc' => $tmFc,
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = data_get($response->json(), 'response.body.items.item', []);
|
||||
|
||||
return $items[0] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 중기날씨 API 호출
|
||||
*/
|
||||
private function fetchMidLandForecast(): array
|
||||
{
|
||||
$tmFc = $this->getMidBaseTmFc();
|
||||
|
||||
$response = Http::timeout(10)->get(self::BASE_MID_LAND, [
|
||||
'serviceKey' => $this->serviceKey,
|
||||
'pageNo' => 1,
|
||||
'numOfRows' => 10,
|
||||
'dataType' => 'JSON',
|
||||
'regId' => self::MID_LAND_REG_ID,
|
||||
'tmFc' => $tmFc,
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = data_get($response->json(), 'response.body.items.item', []);
|
||||
|
||||
return $items[0] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 중기예보 발표시각 (06시/18시 기준)
|
||||
*/
|
||||
private function getMidBaseTmFc(): string
|
||||
{
|
||||
$now = Carbon::now();
|
||||
|
||||
if ($now->hour >= 18) {
|
||||
return $now->format('Ymd').'1800';
|
||||
} elseif ($now->hour >= 6) {
|
||||
return $now->format('Ymd').'0600';
|
||||
}
|
||||
|
||||
return $now->copy()->subDay()->format('Ymd').'1800';
|
||||
}
|
||||
|
||||
/**
|
||||
* 단기예보 SKY + PTY → 아이콘 이름
|
||||
* PTY 우선: 비/눈/진눈깨비가 있으면 해당 아이콘
|
||||
*/
|
||||
private function shortWeatherToIcon(int $sky, int $pty): string
|
||||
{
|
||||
// PTY (강수형태) 우선
|
||||
if ($pty > 0) {
|
||||
return match ($pty) {
|
||||
1, 4 => 'rain', // 비, 소나기
|
||||
2 => 'sleet', // 비/눈
|
||||
3 => 'snow', // 눈
|
||||
5 => 'rain', // 빗방울
|
||||
6 => 'sleet', // 빗방울/눈날림
|
||||
7 => 'snow', // 눈날림
|
||||
default => 'cloud',
|
||||
};
|
||||
}
|
||||
|
||||
// SKY (하늘상태)
|
||||
return match ($sky) {
|
||||
1 => 'sun', // 맑음
|
||||
3 => 'cloud-sun', // 구름많음
|
||||
4 => 'cloud', // 흐림
|
||||
default => 'sun',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 중기예보 한글 날씨 문자열 → 아이콘 이름
|
||||
*/
|
||||
private function midWeatherToIcon(string $wf): string
|
||||
{
|
||||
if (empty($wf)) {
|
||||
return 'cloud';
|
||||
}
|
||||
|
||||
$wf = mb_strtolower($wf);
|
||||
|
||||
if (str_contains($wf, '눈') && str_contains($wf, '비')) {
|
||||
return 'sleet';
|
||||
}
|
||||
if (str_contains($wf, '눈')) {
|
||||
return 'snow';
|
||||
}
|
||||
if (str_contains($wf, '비') || str_contains($wf, '소나기')) {
|
||||
return 'rain';
|
||||
}
|
||||
if (str_contains($wf, '흐림')) {
|
||||
return 'cloud';
|
||||
}
|
||||
if (str_contains($wf, '구름')) {
|
||||
return 'cloud-sun';
|
||||
}
|
||||
if (str_contains($wf, '맑음')) {
|
||||
return 'sun';
|
||||
}
|
||||
|
||||
return 'cloud';
|
||||
}
|
||||
}
|
||||
@@ -101,4 +101,15 @@
|
||||
'app_url' => env('DEV_APP_URL', 'https://dev.sam.kr'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 기상청 공공데이터포털 API
|
||||
|--------------------------------------------------------------------------
|
||||
| 단기예보 + 중기예보 조합으로 7일 날씨 제공
|
||||
| - KMA_SERVICE_KEY: data.go.kr 인코딩 서비스키
|
||||
*/
|
||||
'kma' => [
|
||||
'service_key' => env('KMA_SERVICE_KEY', ''),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -40,6 +40,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weather Widget -->
|
||||
<div class="mt-6 bg-white rounded-lg shadow p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-base font-semibold text-gray-900 flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/>
|
||||
</svg>
|
||||
주간 날씨
|
||||
</h3>
|
||||
<span class="text-xs text-gray-400">서울</span>
|
||||
</div>
|
||||
<div id="weather-container"
|
||||
hx-get="{{ route('dashboard.weather') }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
{{-- 스켈레톤 로딩 --}}
|
||||
<div class="flex gap-3" style="flex-wrap: nowrap;">
|
||||
@for($i = 0; $i < 7; $i++)
|
||||
<div class="text-center rounded-xl border border-gray-100 px-3 py-4 animate-pulse" style="flex: 1 1 0; min-width: 90px;">
|
||||
<div class="h-3 bg-gray-200 rounded mx-auto mb-1" style="width: 24px;"></div>
|
||||
<div class="h-3 bg-gray-100 rounded mx-auto mb-3" style="width: 32px;"></div>
|
||||
<div class="h-10 w-10 bg-gray-200 rounded-full mx-auto mb-3"></div>
|
||||
<div class="h-4 bg-gray-200 rounded mx-auto mb-1" style="width: 28px;"></div>
|
||||
<div class="h-3 bg-gray-100 rounded mx-auto" style="width: 28px;"></div>
|
||||
</div>
|
||||
@endfor
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Section -->
|
||||
<div class="mt-6 bg-white rounded-lg shadow p-6">
|
||||
<div id="calendar-container"
|
||||
|
||||
79
resources/views/dashboard/partials/weather-icon.blade.php
Normal file
79
resources/views/dashboard/partials/weather-icon.blade.php
Normal file
@@ -0,0 +1,79 @@
|
||||
{{-- 날씨 아이콘 컴포넌트 — @include('dashboard.partials.weather-icon', ['icon' => 'sun']) --}}
|
||||
@php $icon = $icon ?? 'cloud'; @endphp
|
||||
|
||||
@if($icon === 'sun')
|
||||
{{-- 맑음 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<circle cx="32" cy="32" r="12" fill="#FBBF24"/>
|
||||
<g stroke="#FBBF24" stroke-width="3" stroke-linecap="round">
|
||||
<line x1="32" y1="6" x2="32" y2="14"/>
|
||||
<line x1="32" y1="50" x2="32" y2="58"/>
|
||||
<line x1="6" y1="32" x2="14" y2="32"/>
|
||||
<line x1="50" y1="32" x2="58" y2="32"/>
|
||||
<line x1="13.6" y1="13.6" x2="19.3" y2="19.3"/>
|
||||
<line x1="44.7" y1="44.7" x2="50.4" y2="50.4"/>
|
||||
<line x1="13.6" y1="50.4" x2="19.3" y2="44.7"/>
|
||||
<line x1="44.7" y1="19.3" x2="50.4" y2="13.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
@elseif($icon === 'cloud-sun')
|
||||
{{-- 구름많음 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<circle cx="22" cy="22" r="9" fill="#FBBF24"/>
|
||||
<g stroke="#FBBF24" stroke-width="2.5" stroke-linecap="round">
|
||||
<line x1="22" y1="5" x2="22" y2="10"/>
|
||||
<line x1="22" y1="34" x2="22" y2="37"/>
|
||||
<line x1="5" y1="22" x2="10" y2="22"/>
|
||||
<line x1="10" y1="10" x2="13.5" y2="13.5"/>
|
||||
<line x1="34" y1="10" x2="30.5" y2="13.5"/>
|
||||
</g>
|
||||
<path d="M48 44H20a10 10 0 0 1-.7-20 14 14 0 0 1 27.4 4A8 8 0 0 1 48 44z" fill="#E5E7EB" stroke="#D1D5DB" stroke-width="1"/>
|
||||
</svg>
|
||||
|
||||
@elseif($icon === 'cloud')
|
||||
{{-- 흐림 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<path d="M50 46H18a12 12 0 0 1-1-24 16 16 0 0 1 31 4 10 10 0 0 1 2 20z" fill="#D1D5DB" stroke="#9CA3AF" stroke-width="1"/>
|
||||
</svg>
|
||||
|
||||
@elseif($icon === 'rain')
|
||||
{{-- 비 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<path d="M50 36H18a12 12 0 0 1-1-24 16 16 0 0 1 31 4 10 10 0 0 1 2 20z" fill="#D1D5DB" stroke="#9CA3AF" stroke-width="1"/>
|
||||
<g stroke="#60A5FA" stroke-width="2.5" stroke-linecap="round">
|
||||
<line x1="22" y1="40" x2="18" y2="52"/>
|
||||
<line x1="32" y1="40" x2="28" y2="52"/>
|
||||
<line x1="42" y1="40" x2="38" y2="52"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
@elseif($icon === 'snow')
|
||||
{{-- 눈 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<path d="M50 36H18a12 12 0 0 1-1-24 16 16 0 0 1 31 4 10 10 0 0 1 2 20z" fill="#D1D5DB" stroke="#9CA3AF" stroke-width="1"/>
|
||||
<circle cx="21" cy="46" r="2.5" fill="#93C5FD"/>
|
||||
<circle cx="32" cy="44" r="2.5" fill="#93C5FD"/>
|
||||
<circle cx="43" cy="46" r="2.5" fill="#93C5FD"/>
|
||||
<circle cx="26" cy="53" r="2.5" fill="#93C5FD"/>
|
||||
<circle cx="38" cy="53" r="2.5" fill="#93C5FD"/>
|
||||
</svg>
|
||||
|
||||
@elseif($icon === 'sleet')
|
||||
{{-- 진눈깨비 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<path d="M50 36H18a12 12 0 0 1-1-24 16 16 0 0 1 31 4 10 10 0 0 1 2 20z" fill="#D1D5DB" stroke="#9CA3AF" stroke-width="1"/>
|
||||
<g stroke="#60A5FA" stroke-width="2.5" stroke-linecap="round">
|
||||
<line x1="22" y1="40" x2="19" y2="49"/>
|
||||
<line x1="38" y1="40" x2="35" y2="49"/>
|
||||
</g>
|
||||
<circle cx="30" cy="47" r="2.5" fill="#93C5FD"/>
|
||||
<circle cx="46" cy="47" r="2.5" fill="#93C5FD"/>
|
||||
</svg>
|
||||
|
||||
@else
|
||||
{{-- 기본: 흐림 --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="width: 40px; height: 40px;">
|
||||
<path d="M50 46H18a12 12 0 0 1-1-24 16 16 0 0 1 31 4 10 10 0 0 1 2 20z" fill="#D1D5DB" stroke="#9CA3AF" stroke-width="1"/>
|
||||
</svg>
|
||||
@endif
|
||||
55
resources/views/dashboard/partials/weather.blade.php
Normal file
55
resources/views/dashboard/partials/weather.blade.php
Normal file
@@ -0,0 +1,55 @@
|
||||
{{-- 주간 날씨 위젯 (HTMX 파티셜) --}}
|
||||
@if(empty($forecasts))
|
||||
<div class="flex items-center justify-center py-6 text-sm text-gray-400">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/>
|
||||
</svg>
|
||||
날씨 정보를 불러올 수 없습니다
|
||||
</div>
|
||||
@else
|
||||
@php
|
||||
$today = now()->format('Ymd');
|
||||
$dayNames = ['일', '월', '화', '수', '목', '금', '토'];
|
||||
@endphp
|
||||
<div class="flex gap-3 overflow-x-auto pb-1" style="flex-wrap: nowrap;">
|
||||
@foreach($forecasts as $fc)
|
||||
@php
|
||||
$dt = \Carbon\Carbon::createFromFormat('Ymd', $fc['date']);
|
||||
$isToday = ($fc['date'] === $today);
|
||||
$dayName = $dayNames[$dt->dayOfWeek];
|
||||
$isSunday = $dt->dayOfWeek === 0;
|
||||
$isSaturday = $dt->dayOfWeek === 6;
|
||||
@endphp
|
||||
<div class="text-center rounded-xl border px-3 py-4 transition-shadow hover:shadow-md
|
||||
{{ $isToday ? 'bg-blue-50 border-blue-300 shadow-sm' : 'bg-white border-gray-200' }}"
|
||||
style="flex: 1 1 0; min-width: 90px;">
|
||||
{{-- 요일 --}}
|
||||
<div class="text-xs font-semibold mb-0.5
|
||||
{{ $isToday ? 'text-blue-700' : ($isSunday ? 'text-red-500' : ($isSaturday ? 'text-blue-500' : 'text-gray-700')) }}">
|
||||
{{ $isToday ? '오늘' : $dayName }}
|
||||
</div>
|
||||
{{-- 날짜 --}}
|
||||
<div class="text-xs {{ $isToday ? 'text-blue-500' : 'text-gray-400' }} mb-2">
|
||||
{{ $dt->format('m.d') }}
|
||||
</div>
|
||||
{{-- 날씨 아이콘 --}}
|
||||
<div class="flex justify-center mb-2">
|
||||
@include('dashboard.partials.weather-icon', ['icon' => $fc['icon'] ?? 'cloud'])
|
||||
</div>
|
||||
{{-- 최고/최저 기온 --}}
|
||||
<div class="space-y-0.5">
|
||||
@if($fc['tmx'] !== null)
|
||||
<div class="text-sm font-bold text-red-500">{{ $fc['tmx'] }}°</div>
|
||||
@else
|
||||
<div class="text-sm text-gray-300">-</div>
|
||||
@endif
|
||||
@if($fc['tmn'] !== null)
|
||||
<div class="text-xs text-blue-500">{{ $fc['tmn'] }}°</div>
|
||||
@else
|
||||
<div class="text-xs text-gray-300">-</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@@ -656,6 +656,9 @@
|
||||
return view('dashboard.index');
|
||||
})->name('dashboard');
|
||||
|
||||
// 대시보드 날씨
|
||||
Route::get('/dashboard/weather', [\App\Http\Controllers\DashboardWeatherController::class, 'weather'])->name('dashboard.weather');
|
||||
|
||||
// 대시보드 달력
|
||||
Route::get('/dashboard/calendar', [\App\Http\Controllers\DashboardCalendarController::class, 'calendar'])->name('dashboard.calendar');
|
||||
Route::post('/dashboard/schedules', [\App\Http\Controllers\DashboardCalendarController::class, 'store'])->name('dashboard.schedules.store');
|
||||
|
||||
Reference in New Issue
Block a user