View.cshtml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. @page "{id:int}"
  2. @model Admin.Pages.Channel.List.ViewModel
  3. @{
  4. ViewData["Title"] = "채널 정보";
  5. }
  6. <div class="container">
  7. <h3>@ViewData["Title"]</h3>
  8. <hr />
  9. <partial name="_StatusMessage" />
  10. @* ── DB 채널 정보 ───────────────────────────────────────────── *@
  11. <h5 class="mt-3 mb-2"><i class="bi bi-database"></i> 채널 기본 정보</h5>
  12. <div class="overflow-hidden border rounded mb-4">
  13. <div class="row g-0 border-bottom">
  14. <div class="col-12 col-md-2 fw-bold p-2 bg-light">PK</div>
  15. <div class="col-12 col-md-10 p-2">@Model.ID</div>
  16. </div>
  17. <div class="row g-0 border-bottom">
  18. <div class="col-12 col-md-2 fw-bold p-2 bg-light">회원(소유자)</div>
  19. <div class="col-12 col-md-10 p-2">@Model.MemberInfo</div>
  20. </div>
  21. <div class="row g-0 border-bottom">
  22. <div class="col-12 col-md-2 fw-bold p-2 bg-light">SID</div>
  23. <div class="col-12 col-md-10 p-2"><code>@Model.SID</code></div>
  24. </div>
  25. <div class="row g-0 border-bottom">
  26. <div class="col-12 col-md-2 fw-bold p-2 bg-light">이름</div>
  27. <div class="col-12 col-md-10 p-2">@Model.Name</div>
  28. </div>
  29. <div class="row g-0 border-bottom">
  30. <div class="col-12 col-md-2 fw-bold p-2 bg-light">핸들</div>
  31. <div class="col-12 col-md-10 p-2">@(Model.Handle ?? "-")</div>
  32. </div>
  33. <div class="row g-0 border-bottom">
  34. <div class="col-12 col-md-2 fw-bold p-2 bg-light">YouTube 주소</div>
  35. <div class="col-12 col-md-10 p-2">
  36. <a href="@Model.YouTubeUrl" target="_blank" rel="external">@Model.YouTubeUrl</a>
  37. </div>
  38. </div>
  39. <div class="row g-0 border-bottom">
  40. <div class="col-12 col-md-2 fw-bold p-2 bg-light">수수료(%)</div>
  41. <div class="col-12 col-md-10 p-2">@Model.PlatformFeeRate%</div>
  42. </div>
  43. <div class="row g-0 border-bottom">
  44. <div class="col-12 col-md-2 fw-bold p-2 bg-light">인증 여부</div>
  45. <div class="col-12 col-md-10 p-2">
  46. @if (Model.IsVerified)
  47. {
  48. <span class="badge bg-success">인증됨</span>
  49. }
  50. else
  51. {
  52. <span class="badge bg-secondary">미인증</span>
  53. }
  54. </div>
  55. </div>
  56. <div class="row g-0 border-bottom">
  57. <div class="col-12 col-md-2 fw-bold p-2 bg-light">사용 여부</div>
  58. <div class="col-12 col-md-10 p-2">
  59. @if (Model.IsActive)
  60. {
  61. <span class="badge bg-primary">활성</span>
  62. }
  63. else
  64. {
  65. <span class="badge bg-danger">비활성</span>
  66. }
  67. </div>
  68. </div>
  69. <div class="row g-0 border-bottom">
  70. <div class="col-12 col-md-2 fw-bold p-2 bg-light">수정일시</div>
  71. <div class="col-12 col-md-10 p-2">@(Model.UpdatedAt ?? "-")</div>
  72. </div>
  73. <div class="row g-0">
  74. <div class="col-12 col-md-2 fw-bold p-2 bg-light">등록일시</div>
  75. <div class="col-12 col-md-10 p-2">@Model.CreatedAt</div>
  76. </div>
  77. </div>
  78. @* ── YouTube API 채널 정보 ───────────────────────────────────── *@
  79. <h5 class="mt-4 mb-2"><i class="bi bi-youtube"></i> YouTube 채널 상세 정보</h5>
  80. @if (Model.YouTubeApiFailed)
  81. {
  82. <div class="alert alert-warning mt-3">
  83. <i class="bi bi-exclamation-triangle"></i> @Model.YouTubeApiError
  84. </div>
  85. }
  86. else if (Model.YouTubeChannel is not null)
  87. {
  88. var yt = Model.YouTubeChannel;
  89. <div class="border rounded mb-4">
  90. @* 채널 프로필 헤더 *@
  91. <div class="d-flex align-items-center p-3 bg-light border-bottom">
  92. <img src="@yt.ThumbnailUrl" alt="@yt.Title" class="rounded-circle me-3"
  93. style="width:64px; height:64px; object-fit:cover;" />
  94. <div>
  95. <h5 class="mb-0">@yt.Title</h5>
  96. @if (yt.CustomUrl is not null)
  97. {
  98. <a href="https://youtube.com/@@(yt.CustomUrl.TrimStart('@@'))" target="_blank"
  99. class="text-muted small">
  100. @@(yt.CustomUrl.TrimStart('@@'))
  101. </a>
  102. }
  103. </div>
  104. </div>
  105. @* 통계 카드 *@
  106. <div class="row g-0 text-center border-bottom">
  107. <div class="col-4 p-3 border-end">
  108. <div class="fs-4 fw-bold">@FormatNumber(yt.SubscriberCount)</div>
  109. <div class="text-muted small">구독자</div>
  110. </div>
  111. <div class="col-4 p-3 border-end">
  112. <div class="fs-4 fw-bold">@FormatNumber(yt.VideoCount)</div>
  113. <div class="text-muted small">동영상</div>
  114. </div>
  115. <div class="col-4 p-3">
  116. <div class="fs-4 fw-bold">@FormatNumber(yt.ViewCount)</div>
  117. <div class="text-muted small">총 조회수</div>
  118. </div>
  119. </div>
  120. @* 상세 정보 테이블 *@
  121. <div class="row g-0 border-bottom">
  122. <div class="col-12 col-md-2 fw-bold p-2 bg-light">채널 ID</div>
  123. <div class="col-12 col-md-10 p-2">
  124. <code>@yt.ChannelId</code>
  125. <a href="https://youtube.com/channel/@yt.ChannelId" target="_blank" class="ms-2 small">
  126. <i class="bi bi-box-arrow-up-right"></i>
  127. </a>
  128. </div>
  129. </div>
  130. <div class="row g-0 border-bottom">
  131. <div class="col-12 col-md-2 fw-bold p-2 bg-light">채널 이름</div>
  132. <div class="col-12 col-md-10 p-2">@yt.Title</div>
  133. </div>
  134. <div class="row g-0 border-bottom">
  135. <div class="col-12 col-md-2 fw-bold p-2 bg-light">커스텀 URL</div>
  136. <div class="col-12 col-md-10 p-2">@(yt.CustomUrl ?? "-")</div>
  137. </div>
  138. <div class="row g-0 border-bottom">
  139. <div class="col-12 col-md-2 fw-bold p-2 bg-light">구독자 수</div>
  140. <div class="col-12 col-md-10 p-2">@yt.SubscriberCount.ToString("N0")</div>
  141. </div>
  142. <div class="row g-0 border-bottom">
  143. <div class="col-12 col-md-2 fw-bold p-2 bg-light">동영상 수</div>
  144. <div class="col-12 col-md-10 p-2">@yt.VideoCount.ToString("N0")</div>
  145. </div>
  146. <div class="row g-0 border-bottom">
  147. <div class="col-12 col-md-2 fw-bold p-2 bg-light">총 조회수</div>
  148. <div class="col-12 col-md-10 p-2">@yt.ViewCount.ToString("N0")</div>
  149. </div>
  150. <div class="row g-0 border-bottom">
  151. <div class="col-12 col-md-2 fw-bold p-2 bg-light">썸네일</div>
  152. <div class="col-12 col-md-10 p-2">
  153. <img src="@yt.ThumbnailUrl" alt="thumbnail" class="rounded" style="max-width:120px;" />
  154. </div>
  155. </div>
  156. <div class="row g-0">
  157. <div class="col-12 col-md-2 fw-bold p-2 bg-light">설명</div>
  158. <div class="col-12 col-md-10 p-2">
  159. @if (!string.IsNullOrWhiteSpace(yt.Description))
  160. {
  161. <div style="max-height:200px; overflow-y:auto; white-space:pre-wrap;">@yt.Description</div>
  162. }
  163. else
  164. {
  165. <span class="text-muted">-</span>
  166. }
  167. </div>
  168. </div>
  169. </div>
  170. }
  171. @* ── 생방송 방송 상태 (PubSub 기반 — Redis 조회, API 0 unit) ──── *@
  172. <h5 class="mt-4 mb-2"><i class="bi bi-broadcast"></i> 생방송 상태</h5>
  173. @if (Model.LiveStream is not null)
  174. {
  175. var live = Model.LiveStream;
  176. <div class="border rounded mb-4 border-danger">
  177. <div class="d-flex align-items-center p-3 bg-danger bg-opacity-10 border-bottom">
  178. <span class="badge bg-danger me-2 fs-6">LIVE</span>
  179. <h6 class="mb-0">@live.Title</h6>
  180. </div>
  181. <div class="row g-0 border-bottom">
  182. <div class="col-12 col-md-2 fw-bold p-2 bg-light">Video ID</div>
  183. <div class="col-12 col-md-10 p-2">
  184. <code>@live.VideoId</code>
  185. <a href="https://youtube.com/watch?v=@live.VideoId" target="_blank" class="ms-2 small">
  186. <i class="bi bi-box-arrow-up-right"></i>
  187. </a>
  188. </div>
  189. </div>
  190. <div class="row g-0 border-bottom">
  191. <div class="col-12 col-md-2 fw-bold p-2 bg-light">Live Chat ID</div>
  192. <div class="col-12 col-md-10 p-2"><code>@(live.ActiveLiveChatId ?? "-")</code></div>
  193. </div>
  194. <div class="row g-0 border-bottom">
  195. <div class="col-12 col-md-2 fw-bold p-2 bg-light">방송 시작</div>
  196. <div class="col-12 col-md-10 p-2">@(live.ActualStartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-")</div>
  197. </div>
  198. <div class="row g-0">
  199. <div class="col-12 col-md-2 fw-bold p-2 bg-light">상태</div>
  200. <div class="col-12 col-md-10 p-2">
  201. @if (live.IsLive)
  202. {
  203. <span class="badge bg-danger">생방송 중</span>
  204. }
  205. else if (live.IsUpcoming)
  206. {
  207. <span class="badge bg-warning text-dark">예정됨 — @(live.ScheduledStartTime?.ToString("yyyy-MM-dd HH:mm") ?? "")</span>
  208. }
  209. </div>
  210. </div>
  211. </div>
  212. }
  213. else
  214. {
  215. <div class="border rounded p-3 mb-4 text-muted bg-light">
  216. 방송 종료
  217. </div>
  218. }
  219. <div class="d-grid gap-2 text-center d-md-block mt-3">
  220. <a href="/Channel/List/Edit/@Model.ID" class="btn btn-info text-white">수정</a>
  221. <a href="/Channel/List" class="btn btn-secondary">목록</a>
  222. </div>
  223. <br />
  224. <br />
  225. </div>
  226. @section Styles {
  227. <style>
  228. @@media (min-width: 768px) {
  229. .border-end-md {
  230. border-right: 1px solid var(--bs-border-color) !important;
  231. }
  232. }
  233. </style>
  234. }
  235. @functions {
  236. static string FormatNumber(long value)
  237. {
  238. if (value >= 100_000_000)
  239. {
  240. return $"{value / 100_000_000.0:0.#}억";
  241. }
  242. if (value >= 10_000)
  243. {
  244. return $"{value / 10_000.0:0.#}만";
  245. }
  246. if (value >= 1_000)
  247. {
  248. return $"{value / 1_000.0:0.#}천";
  249. }
  250. return value.ToString("N0");
  251. }
  252. }