feat: 블로그 시즌 연동 — 홈페이지 세무 정보 섹션 시즌화

- TaxSeason / CurrentSeasonDto에 RelatedCategorySlug 추가
- TaxSeasonCalendar 각 시즌에 카테고리 슬러그 매핑
  (income-tax→income-tax, vat-1st/2nd→vat, 종부세→real-estate-tax 등)
- IBlogPostRepository.GetByCategorySlugAsync 추가
- BlogService.GetSeasonalPostsAsync: 시즌 관련 글 2개 우선 + 나머지 최신 글로 채움
- IndexModel: SeasonalPosts / RecentPosts 분리 로드
- Index.cshtml 블로그 섹션: 시즌 중 "이번 시즌 추천" 배지 + 시즌별 전체보기 버튼
- site.css: blog-card--seasonal, seasonal-blog-tag, btn-seasonal 스타일

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 23:11:45 +09:00
parent 1ad720afe6
commit 46951d871a
11 changed files with 187 additions and 24 deletions
+61 -15
View File
@@ -273,33 +273,79 @@ else
</div>
</section>
<!-- 최근 블로그 -->
<!-- 세무 정보 블로그 -->
<section class="py-5">
<div class="container">
<div class="text-center mb-5">
<h2 class="section-title">세무 정보</h2>
<p class="text-muted">최신 세법 변화와 실무 팁을 공유합니다</p>
@if (season != null)
{
<div class="seasonal-blog-header mb-2">
<span class="seasonal-blog-tag">📅 @season.Name 시즌</span>
</div>
<h2 class="section-title">이번 시즌 세무 정보</h2>
<p class="text-muted">@season.Name 관련 절세 팁과 신고 가이드를 확인하세요</p>
}
else
{
<h2 class="section-title">세무 정보</h2>
<p class="text-muted">최신 세법 변화와 실무 팁을 공유합니다</p>
}
</div>
@if (Model.RecentPosts?.Count > 0)
@{
var hasSeasonalPosts = Model.SeasonalPosts?.Count > 0;
var hasRecentPosts = Model.RecentPosts?.Count > 0;
}
@if (hasSeasonalPosts || hasRecentPosts)
{
<div class="row g-4">
@foreach (var post in Model.RecentPosts.Take(3))
@* 시즌 관련 글 (배지 강조) *@
@if (hasSeasonalPosts)
{
<div class="col-lg-4 col-md-6">
<div class="card blog-card h-100">
<div class="blog-placeholder">📝</div>
<div class="card-body">
<small class="badge bg-primary-badge">@post.CategoryName</small>
<h4 class="card-title mt-3">@post.Title</h4>
<p class="text-muted small">@post.CreatedAt.ToString("yyyy년 MM월 dd일")</p>
<a href="/taxbaik/blog/@post.Slug" class="btn btn-sm btn-primary">글 내용 보기</a>
@foreach (var post in Model.SeasonalPosts!)
{
<div class="col-lg-4 col-md-6">
<div class="card blog-card h-100 blog-card--seasonal">
<div class="blog-seasonal-ribbon">이번 시즌 추천</div>
<div class="blog-placeholder">🗓️</div>
<div class="card-body">
<small class="badge bg-season-badge">@post.CategoryName</small>
<h4 class="card-title mt-3">@post.Title</h4>
<p class="text-muted small">@((post.PublishedAt ?? post.CreatedAt).ToString("yyyy년 MM월 dd일"))</p>
<a href="/taxbaik/blog/@post.Slug" class="btn btn-sm btn-seasonal">자세히 보기</a>
</div>
</div>
</div>
</div>
}
}
@* 최신 글 (나머지 채우기) *@
@if (hasRecentPosts)
{
@foreach (var post in Model.RecentPosts!)
{
<div class="col-lg-4 col-md-6">
<div class="card blog-card h-100">
<div class="blog-placeholder">📝</div>
<div class="card-body">
<small class="badge bg-primary-badge">@post.CategoryName</small>
<h4 class="card-title mt-3">@post.Title</h4>
<p class="text-muted small">@((post.PublishedAt ?? post.CreatedAt).ToString("yyyy년 MM월 dd일"))</p>
<a href="/taxbaik/blog/@post.Slug" class="btn btn-sm btn-primary">글 내용 보기</a>
</div>
</div>
</div>
}
}
</div>
<div class="text-center mt-5">
<div class="text-center mt-5 d-flex justify-content-center gap-3 flex-wrap">
@if (season != null && !string.IsNullOrEmpty(season.RelatedCategorySlug))
{
<a href="/taxbaik/blog?category=@season.RelatedCategorySlug" class="btn btn-outline-seasonal btn-lg">
📅 @season.Name 전체 글 보기
</a>
}
<a href="/taxbaik/blog" class="btn btn-outline-primary btn-lg">전체 블로그 보기</a>
</div>
}
+14 -2
View File
@@ -12,6 +12,7 @@ public class IndexModel : PageModel
private readonly AnnouncementService _announcementService;
public List<BlogPost> RecentPosts { get; set; } = [];
public List<BlogPost> SeasonalPosts { get; set; } = [];
public CurrentSeasonDto? CurrentSeason { get; set; }
public List<Announcement> ActiveAnnouncements { get; set; } = [];
@@ -40,12 +41,23 @@ public class IndexModel : PageModel
try
{
var (posts, _) = await _blogService.GetPublishedPagedAsync(1, 3);
RecentPosts = posts.ToList();
if (CurrentSeason is not null && !string.IsNullOrEmpty(CurrentSeason.RelatedCategorySlug))
{
var (seasonal, latest) = await _blogService.GetSeasonalPostsAsync(
CurrentSeason.RelatedCategorySlug, seasonalCount: 2, totalCount: 3);
SeasonalPosts = seasonal.ToList();
RecentPosts = latest.ToList();
}
else
{
var (posts, _) = await _blogService.GetPublishedPagedAsync(1, 3);
RecentPosts = posts.ToList();
}
}
catch
{
RecentPosts = [];
SeasonalPosts = [];
}
}
}
+62
View File
@@ -641,3 +641,65 @@ img {
white-space: nowrap;
letter-spacing: 0.5px;
}
/* ===== 블로그 시즌 연동 ===== */
.seasonal-blog-header {
display: flex;
justify-content: center;
}
.seasonal-blog-tag {
display: inline-block;
background: linear-gradient(135deg, #C62828 0%, #B71C1C 100%);
color: white;
font-size: 0.82rem;
font-weight: 700;
padding: 4px 16px;
border-radius: 20px;
letter-spacing: 0.5px;
}
.blog-card--seasonal {
border: 2px solid var(--color-primary) !important;
position: relative;
overflow: visible;
}
.blog-seasonal-ribbon {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: var(--color-primary);
color: white;
font-size: 0.75rem;
font-weight: 700;
padding: 3px 14px;
border-radius: 20px;
white-space: nowrap;
letter-spacing: 0.5px;
z-index: 1;
}
.bg-season-badge {
background-color: var(--color-primary) !important;
color: white !important;
}
.btn-seasonal {
background-color: var(--color-primary);
color: white;
border: none;
}
.btn-seasonal:hover {
background-color: var(--color-primary-dark);
color: white;
}
.btn-outline-seasonal {
border: 2px solid var(--color-primary);
color: var(--color-primary);
background: transparent;
}
.btn-outline-seasonal:hover {
background-color: var(--color-primary);
color: white;
}