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:
@@ -8,6 +8,7 @@ public record CurrentSeasonDto
|
||||
public string HeroSubtext { get; init; } = "";
|
||||
public string UrgencyBadge { get; init; } = "";
|
||||
public string FocusService { get; init; } = "";
|
||||
public string RelatedCategorySlug { get; init; } = "";
|
||||
public string CtaText { get; init; } = "상담 신청하기";
|
||||
public int DaysUntilDeadline { get; init; }
|
||||
public DateTime Deadline { get; init; }
|
||||
|
||||
@@ -15,4 +15,6 @@ public record TaxSeason
|
||||
public string UrgencyBadge { get; init; } = "";
|
||||
public string FocusService { get; init; } = "";
|
||||
public string CtaText { get; init; } = "상담 신청하기";
|
||||
/// <summary>블로그 시즌 연동 시 우선 노출할 카테고리 slug (categories.slug 참조)</summary>
|
||||
public string RelatedCategorySlug { get; init; } = "";
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "일반과세 사업자 확정신고 · 기한 내 신고로 가산세 방지",
|
||||
UrgencyBadge = "D-{n}일 | 부가세 마감",
|
||||
FocusService = "business-tax",
|
||||
CtaText = "부가세 신고 상담"
|
||||
CtaText = "부가세 신고 상담",
|
||||
RelatedCategorySlug = "vat"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -30,7 +31,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "직원이 있는 사업자 원천징수 신고 · 환급 최대화",
|
||||
UrgencyBadge = "연말정산 진행 중",
|
||||
FocusService = "business-tax",
|
||||
CtaText = "연말정산 상담"
|
||||
CtaText = "연말정산 상담",
|
||||
RelatedCategorySlug = "business-tax"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -42,7 +44,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "법인사업자 결산 · 세무조정 · 절세 전략 수립",
|
||||
UrgencyBadge = "D-{n}일 | 법인세 마감",
|
||||
FocusService = "business-tax",
|
||||
CtaText = "법인세 신고 상담"
|
||||
CtaText = "법인세 신고 상담",
|
||||
RelatedCategorySlug = "business-tax"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -54,7 +57,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "개인사업자 · 임대소득 · 프리랜서 · 기타소득 모두 해당",
|
||||
UrgencyBadge = "D-{n}일 | 종합소득세 마감",
|
||||
FocusService = "business-tax",
|
||||
CtaText = "종합소득세 상담"
|
||||
CtaText = "종합소득세 상담",
|
||||
RelatedCategorySlug = "income-tax"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -66,7 +70,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "일반과세 사업자 1기 확정신고 · 매입세액 공제 점검",
|
||||
UrgencyBadge = "D-{n}일 | 부가세 마감",
|
||||
FocusService = "business-tax",
|
||||
CtaText = "부가세 신고 상담"
|
||||
CtaText = "부가세 신고 상담",
|
||||
RelatedCategorySlug = "vat"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -78,7 +83,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "다주택자 · 임대사업자 세부담 분석 · 분납·합산배제 검토",
|
||||
UrgencyBadge = "D-{n}일 | 종부세 납부",
|
||||
FocusService = "real-estate-tax",
|
||||
CtaText = "종부세 절세 상담"
|
||||
CtaText = "종부세 절세 상담",
|
||||
RelatedCategorySlug = "real-estate-tax"
|
||||
},
|
||||
new TaxSeason
|
||||
{
|
||||
@@ -90,7 +96,8 @@ public static class TaxSeasonCalendar
|
||||
HeroSubtext = "증여 공제 한도 · 자산 이전 · 법인전환 연간 마감",
|
||||
UrgencyBadge = "D-{n}일 | 연간 증여 한도 마감",
|
||||
FocusService = "family-asset",
|
||||
CtaText = "연말 절세 상담"
|
||||
CtaText = "연말 절세 상담",
|
||||
RelatedCategorySlug = "family-asset"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,19 @@ public class BlogService(IBlogPostRepository repository, IMemoryCache memoryCach
|
||||
public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct = default) =>
|
||||
await repository.GetBySlugAsync(slug, ct);
|
||||
|
||||
/// <summary>카테고리 슬러그 기준 시즌 관련 글 조회. 부족 시 최신 글로 채워 total개 반환.</summary>
|
||||
public async Task<(IEnumerable<BlogPost> Seasonal, IEnumerable<BlogPost> Latest)> GetSeasonalPostsAsync(
|
||||
string categorySlug, int seasonalCount, int totalCount, CancellationToken ct = default)
|
||||
{
|
||||
var seasonal = (await repository.GetByCategorySlugAsync(categorySlug, seasonalCount, ct)).ToList();
|
||||
var seasonalIds = seasonal.Select(p => p.Id).ToHashSet();
|
||||
|
||||
var (latestAll, _) = await repository.GetPublishedPagedAsync(1, totalCount + seasonalCount, null, ct);
|
||||
var latest = latestAll.Where(p => !seasonalIds.Contains(p.Id)).Take(totalCount - seasonal.Count).ToList();
|
||||
|
||||
return (seasonal, latest);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<BlogPost>, int)> GetPublishedPagedAsync(
|
||||
int page, int pageSize, int? categoryId = null, CancellationToken ct = default) =>
|
||||
await repository.GetPublishedPagedAsync(NormalizePage(page), NormalizePageSize(pageSize), categoryId, ct);
|
||||
|
||||
@@ -24,6 +24,7 @@ public class SeasonalMarketingService
|
||||
HeroSubtext = season.HeroSubtext,
|
||||
UrgencyBadge = season.UrgencyBadge.Replace("{n}", days.ToString()),
|
||||
FocusService = season.FocusService,
|
||||
RelatedCategorySlug = season.RelatedCategorySlug,
|
||||
CtaText = season.CtaText,
|
||||
DaysUntilDeadline = days,
|
||||
Deadline = end
|
||||
|
||||
Reference in New Issue
Block a user