Files
xrlms/src/app/page.tsx
2025-11-18 06:19:26 +09:00

380 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @next/next/no-img-element */
'use client';
import { useEffect, useMemo, useRef, useState } from 'react';
import MainLogoSvg from './svgs/mainlogosvg';
export default function Home() {
const containerRef = useRef<HTMLDivElement | null>(null);
const [currentIndex, setCurrentIndex] = useState(0);
// 코스, 공지사항 더미 데이터
const courseCards = useMemo(
() =>
[
{
id: 'c1',
title: '원자력 운영 기초',
meta: 'VOD • 초급 • 4시간 20분',
image:
'https://images.unsplash.com/photo-1555949963-ff9fe0c870eb?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c2',
title: '반도체',
meta: 'VOD • 중급 • 3시간 10분',
image:
'https://images.unsplash.com/photo-1581092921461-eab62e97a780?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c3',
title: '방사선 안전',
meta: 'VOD • 중급 • 4시간 20분',
image:
'https://images.unsplash.com/photo-1581090464777-f3220bbe1b8b?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c4',
title: '방사선 폐기물',
meta: 'VOD • 중급 • 4시간 20분',
image:
'https://images.unsplash.com/photo-1581091220351-5a6a4e6f22c1?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c5',
title: '원자력 운전 개론',
meta: 'VOD • 초급 • 3시간 00분',
image:
'https://images.unsplash.com/photo-1581090463520-5d09f3c456d2?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c6',
title: '안전 표지와 표준',
meta: 'VOD • 초급 • 2시간 40분',
image:
'https://images.unsplash.com/photo-1470167290877-7d5d3446de4c?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c7',
title: '발전소 운영',
meta: 'VOD • 중급 • 4시간 20분',
image:
'https://images.unsplash.com/photo-1581092160562-40aa08e78837?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c8',
title: '방사선 안전 실습',
meta: 'VOD • 중급 • 3시간 30분',
image:
'https://images.unsplash.com/photo-1581093458791-9d181f5842fd?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c9',
title: '실험실 안전',
meta: 'VOD • 초급 • 2시간 10분',
image:
'https://images.unsplash.com/photo-1559757175-08c6d5b3f4b4?q=80&w=1200&auto=format&fit=crop',
},
{
id: 'c10',
title: '기초 장비 운용',
meta: 'VOD • 초급 • 2시간 50분',
image:
'https://images.unsplash.com/photo-1581092338398-16e5b28a2b13?q=80&w=1200&auto=format&fit=crop',
},
] as Array<{ id: string; title: string; meta: string; image: string }>,
[]
);
const noticeRows = useMemo(
() =>
[
{ id: 5, title: '(공지)시스템 개선이 완료되었...', date: '2025-09-10', views: 1320, writer: '운영팀' },
{ id: 4, title: '(공지)서버 점검 안내(9/10 새벽)', date: '2025-09-10', views: 1210, writer: '운영팀' },
{ id: 3, title: '(공지)서비스 개선 안내', date: '2025-09-10', views: 1230, writer: '운영팀' },
{ id: 2, title: '(공지)시장점검 공지', date: '2025-09-10', views: 1320, writer: '관리자' },
{ id: 1, title: '뉴: 봉사시간 안내 및 한눈에 보는 현황 정리', date: '2025-08-28', views: 594, writer: '운영팀' },
],
[]
);
// NOTE: 실제 이미지 자산 연결 시 해당 src를 교체하세요.
const slides = useMemo(
() => [
{
id: 1,
title: '시스템 점검 안내',
description: '11월 10일 새벽 2시~4시 시스템 점검이 진행됩니다.',
imageSrc:
'https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?q=80&w=1600&auto=format&fit=crop',
},
{
id: 2,
title: '신규 과정 오픈',
description: '최신 커리큘럼으로 업스킬링하세요.',
imageSrc:
'https://images.unsplash.com/photo-1550602921-a0d9a4d5b1a9?q=80&w=1600&auto=format&fit=crop',
},
{
id: 3,
title: '수강 이벤트',
description: '이번 달 수강 혜택을 확인해보세요.',
imageSrc:
'https://images.unsplash.com/photo-1545235617-9465d2a55698?q=80&w=1600&auto=format&fit=crop',
},
],
[]
);
useEffect(() => {
const containerEl = containerRef.current;
if (!containerEl) return;
const handleScroll = () => {
const width = containerEl.clientWidth;
const index = Math.round(containerEl.scrollLeft / Math.max(width, 1));
setCurrentIndex(index);
};
containerEl.addEventListener('scroll', handleScroll, { passive: true });
return () => {
containerEl.removeEventListener('scroll', handleScroll);
};
}, []);
const scrollToIndex = (index: number) => {
const containerEl = containerRef.current;
if (!containerEl) return;
const clamped = Math.max(0, Math.min(index, slides.length - 1));
const width = containerEl.clientWidth;
containerEl.scrollTo({ left: clamped * width, behavior: 'smooth' });
};
const handlePrev = () => scrollToIndex(currentIndex - 1);
const handleNext = () => scrollToIndex(currentIndex + 1);
return (
<div className="w-full min-h-screen flex flex-col bg-white">
<main className="flex-1">
{/* 메인 컨테이너 */}
<div className="w-full flex justify-center">
<div className="w-full max-w-[1180px] px-3 py-6">
{/* 배너 + 사이드 */}
<div className="flex gap-6">
{/* 배너 */}
<section className="flex-1" aria-label="홈 상단 배너">
<div className="relative">
<div
ref={containerRef}
className="flex overflow-x-auto snap-x snap-mandatory scroll-smooth rounded-xl bg-[#F1F3F5]"
>
{slides.map((slide) => (
<div key={slide.id} className="flex-none w-full h-[360px] relative snap-start overflow-hidden">
<img alt={slide.title} src={slide.imageSrc} className="w-full h-full object-cover block" />
<div className="absolute left-0 right-0 bottom-0 h-1/2 bg-linear-to-b from-transparent via-black/55 to-black/75" />
<div className="absolute left-6 bottom-6 text-white">
<div className="font-bold text-[20px] leading-normal mb-1.5">{slide.title}</div>
<div className="font-medium text-[14px] leading-normal opacity-95">
{slide.description}
</div>
</div>
</div>
))}
</div>
{/* 좌/우 내비게이션 버튼 */}
<button
type="button"
onClick={handlePrev}
aria-label="이전 배너"
disabled={currentIndex <= 0}
className="absolute left-2 top-1/2 -translate-y-1/2 w-9 h-9 rounded-full bg-white/90 text-[#1B2027] border border-[#DEE1E6] flex items-center justify-center cursor-pointer shadow disabled:opacity-60"
>
</button>
<button
type="button"
onClick={handleNext}
aria-label="다음 배너"
disabled={currentIndex >= slides.length - 1}
className="absolute right-2 top-1/2 -translate-y-1/2 w-9 h-9 rounded-full bg-white/90 text-[#1B2027] border border-[#DEE1E6] flex items-center justify-center cursor-pointer shadow disabled:opacity-60"
>
</button>
{/* 인디케이터 */}
<div className="absolute left-1/2 bottom-3 -translate-x-1/2 flex gap-1.5" aria-hidden>
{slides.map((_, idx) => (
<span
key={idx}
className={'w-1.5 h-1.5 rounded-full ' + (idx === currentIndex ? 'bg-white' : 'bg-white/50')}
/>
))}
</div>
</div>
</section>
{/* 사이드 패널 (피그마 디자인 적용) */}
<aside className="flex-none w-[400px]">
<div className="bg-[#F1F3F5] rounded-[12px] overflow-hidden h-[510px]">
{/* 상단 환영 및 통계 */}
<div className="px-8 py-8">
<div className="mb-6">
<div className="text-[18px] leading-normal">
<span className="font-bold text-[#333C47]"></span>
<span className="font-normal text-[#333C47]"> .</span>
</div>
</div>
<div className="flex items-center justify-center gap-6">
{[
{ label: '수강중', value: 0 },
{ label: '수강 완료', value: 0 },
{ label: '문제 제출', value: 0 },
{ label: '수료증 발급', value: 0 },
].map((s) => (
<div key={s.label} className="w-[64px] flex flex-col items-center justify-center gap-2">
<div className="size-16 rounded-full bg-white flex items-center justify-center">
<div className="text-[20px] font-bold text-[#333C47] leading-none">{s.value}</div>
</div>
<div className="text-[15px] font-semibold text-[#333C47] leading-none">{s.label}</div>
</div>
))}
</div>
</div>
{/* 구분선 */}
<div className="h-px bg-[#DEE1E6] mx-8" />
{/* 최근 수강 내역 */}
<div className="px-8 pt-3 pb-6">
<div className="h-[60px] w-full flex items-center justify-between">
<div className="text-[18px] font-bold text-[#1B2027]"> </div>
<a href="#" className="flex items-center gap-0.5 text-[14px] font-medium text-[#6C7682] no-underline">
<svg width="16" height="16" viewBox="0 0 24 24" aria-hidden>
<path d="M8 5l8 7-8 7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</div>
<div className="flex flex-col gap-2">
{[
{ cat: '원자로 운전 및 계통', title: '6. 원자로 시동, 운전 및 정지 절차' },
{ cat: '원자로 운전 및 계통', title: '6. 원자로 시동, 운전 및 정지 절차' },
{ cat: '원자로 운전 및 계통', title: '6. 원자로 시동, 운전 및 정지 절차' },
].map((r, i) => (
<div key={i} className="w-full rounded-[8px] bg-white px-5 py-3">
<div className="text-[14px] font-semibold leading-normal">
<p className="text-[#6C7682]">{r.cat}</p>
<p className="text-[#333C47] whitespace-pre">{r.title}</p>
</div>
</div>
))}
</div>
</div>
</div>
</aside>
</div>
{/* 교육 과정 */}
<section className="mt-8">
<div className="flex items-center justify-between mb-3">
<div className="flex items-baseline gap-2">
<h2 className="m-0 text-[18px] font-bold text-[#333C47]">
</h2>
<span className="text-[#8C95A1] text-[13px]"> 28</span>
</div>
<a href="#" className="text-[#8C95A1] text-[13px] no-underline">
</a>
</div>
<div className="grid grid-cols-5 gap-4">
{courseCards.map((c) => (
<article key={c.id} className="bg-white border border-[#DEE1E6] rounded-[10px] overflow-hidden">
<div className="w-full h-[120px] overflow-hidden">
<img alt={c.title} src={c.image} className="w-full h-full object-cover" />
</div>
<div className="p-2.5">
<div className="text-[#333C47] font-semibold text-[14px] leading-[1.35] mb-1.5" title={c.title}>
{c.title}
</div>
<div className="text-[#8C95A1] text-[12px]">{c.meta}</div>
</div>
</article>
))}
</div>
</section>
{/* 공지사항 */}
<section className="mt-9">
<div className="flex items-center justify-between mb-3">
<div className="flex items-baseline gap-2">
<h2 className="m-0 text-[16px] font-bold text-[#333C47]">
</h2>
<span className="text-[#8C95A1] text-[13px]"> 102</span>
</div>
<a href="#" className="text-[#8C95A1] text-[13px] no-underline">
</a>
</div>
<div className="bg-white border border-[#DEE1E6] rounded-[10px] overflow-hidden">
<div className="grid grid-cols-[80px_1fr_140px_100px_90px] gap-0 bg-[#F9FAFB] text-[#6C7682] text-[13px] border-b border-[#DEE1E6]">
{['번호', '제목', '작성일', '조회수', '작성자'].map((h) => (
<div key={h} className="py-2.5 px-3">
{h}
</div>
))}
</div>
<div>
{noticeRows.map((r, idx) => (
<div
key={r.id}
className={
'grid grid-cols-[80px_1fr_140px_100px_90px] gap-0 text-[14px] text-[#4C5561] ' +
(idx === noticeRows.length - 1 ? '' : 'border-b border-[#F1F3F5]')
}
>
<div className="py-3 px-3">{r.id}</div>
<div className="py-3 px-3 whitespace-nowrap overflow-hidden text-ellipsis" title={r.title}>
{r.title}
</div>
<div className="py-3 px-3">{r.date}</div>
<div className="py-3 px-3">{r.views}</div>
<div className="py-3 px-3">{r.writer}</div>
</div>
))}
</div>
</div>
</section>
</div>
</div>
</main>
{/* 푸터 */}
<footer className="mt-10 bg-[#F1F3F5] border-t border-[#DEE1E6]">
<div className="max-w-[1180px] mx-auto px-3 pt-6 pb-10 text-[#4C5561]">
<a href="/" aria-label="XR LMS 홈" className="flex items-center gap-3 no-underline">
<MainLogoSvg width={46.703} height={36} />
<strong className="text-[#333C47]">XL LMS</strong>
</a>
<div className="flex flex-wrap gap-4 mt-3 text-[13px]">
<a href="#" className="text-[#4C5561] no-underline"> </a>
<a href="#" className="text-[#4C5561] no-underline"></a>
<a href="#" className="text-[#4C5561] no-underline"></a>
</div>
<div className="mt-3 text-[12px] text-[#8C95A1]">
(12345) 123-12() | 전화: 1234-1234 ( 09:00 ~ 18:00) | 이메일: xper1234@xpl.co.kr
</div>
<div className="mt-2 text-[12px] text-[#8C95A1]">
Copyright © 2025 XL LMS. All rights reserved
</div>
</div>
</footer>
</div>
);
}