Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 23 additions & 27 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "trailine-web",
"type": "module",
"version": "0.2.0",
"version": "0.3.0",
"scripts": {
"dev": "astro dev",
"build": "astro build",
Expand All @@ -11,7 +11,7 @@
"test:run": "vitest run"
},
"dependencies": {
"@astrojs/node": "^10.0.0",
"@astrojs/node": "^9.5.1",
"@astrojs/react": "^4.4.2",
"@tailwindcss/vite": "^4.1.17",
"@types/navermaps": "^3.9.1",
Expand Down
23 changes: 15 additions & 8 deletions web/src/components/client/CourseIntervalDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,32 @@ const CourseIntervalDetail: React.FC<Props> = ({courseId} : Props) => {
return (
<div className="lg:flex lg:gap-10 w-full">
<CourseMap intervalCount={intervalCount} intervals={intervals} className="w-full h-[600px] lg:w-full lg:flex-1 lg:min-w-0" />
<ul className="mt-10 lg:flex-1 lg:min-w-0 flex flex-col gap-y-3">
<ul className="mt-10 lg:mt-0 lg:flex-1 lg:min-w-0 flex flex-col gap-y-4">
{intervals.map((interval, idx) => (
<li>
<div className="collapse bg-base-100 border-base-300 border">
<input type="checkbox" />
<div
className="collapse-title font-semibold text-white lg:w-auto w-full"
style={{background: INTERVAL_DIFFICULTY_COLORS[interval.difficulty.level]}}>{
idx + 1}. {interval.name} [{interval.difficulty.name}] ({interval.length} km, {minutesToKoreanDuration(interval.duration)})
<div className="collapse-title flex items-center gap-3 lg:w-auto w-full">
<div
className="text-white text-xs font-bold px-2 py-1 rounded-md shrink-0"
style={{backgroundColor: INTERVAL_DIFFICULTY_COLORS[interval.difficulty.level]}}
>
{interval.difficulty.name}
</div>
<div className="min-w-0">
<p className="font-semibold truncate">{idx + 1}. {interval.name}</p>
<p className="text-xs text-base-content/60">{interval.length} km · {minutesToKoreanDuration(interval.duration)}</p>
</div>
</div>
<div className="collapse-content text-sm pt-5">
<div className="collapse-content text-sm">
<p className="mb-4">{interval.description}</p>
{interval.images && interval.images.length > 0 && (
<ImageSlider images={interval.images.map((image) => ({
url: image.url,
title: image.title ?? "",
description: image.description,
}))} className="max-w-full mx-auto mb-4" height={400} />
}))} className="max-w-full mx-auto" heightClassName="h-[200px] lg:h-[400px]" />
)}
<p>{interval.description}</p>
</div>
</div>
</li>
Expand Down
66 changes: 32 additions & 34 deletions web/src/components/client/CourseSearchList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,44 +57,42 @@ const CourseSearchList: React.FC = () => {
const paginationItems = getPaginationItems(currentPage, totalPages);

return (
<div className="mt-5 overflow-xauto rounded-box bg-base-100 w-full lg:w-[700px]">
<table className="table border border-base-content/5">
<thead>
<tr>
<th></th>
<th>코스명</th>
<th>주소</th>
<th>코스 스타일</th>
<th>난이도</th>
</tr>
</thead>
<tbody>
{courses.map((course, index) => (
<tr key={course.id}>
<th>{((currentPage - 1) * (searchResult?.pageSize || 10)) + index + 1}</th>
<td><a href={`/courses/${course.id}`} className="hover:underline">{course.name}</a></td>
<td>
{course.roadAddresses.length > 0 ? (
course.roadAddresses[0]
) : (
course.loadAddresses[0]
)}
</td>
<td>{course.courseStyle.name}</td>
<td><div
className="text-white px-2 py-1.5 rounded-md text-center w-15 text-base"
style={{ backgroundColor: COURSE_DIFFICULTY_COLORS[course.difficulty.level] }}
>Lv.{course.difficulty.level}</div></td>
</tr>
))}
</tbody>
</table>

<div className="mt-5 w-full lg:w-[700px]">
{/* Course Card List */}
<div className="flex flex-col gap-3">
{courses.map((course) => (
<a
key={course.id}
href={`/courses/${course.id}`}
className="flex items-center gap-4 p-4 rounded-box border border-base-content/10 bg-base-100 hover:shadow-md transition-shadow"
>
{/* Difficulty Badge */}
<div
className="text-white px-2 py-1.5 rounded-md text-center min-w-12 text-sm font-bold shrink-0"
style={{ backgroundColor: COURSE_DIFFICULTY_COLORS[course.difficulty.level] }}
>
Lv.{course.difficulty.level}
</div>

{/* Course Info */}
<div className="flex-1 min-w-0">
<div className="font-bold text-base truncate">{course.name}</div>
<div className="text-sm text-base-content/60 truncate">
{course.roadAddresses.length > 0 ? course.roadAddresses[0] : course.loadAddresses[0]}
</div>
</div>

{/* Course Style Tag */}
<div className="badge badge-outline shrink-0">{course.courseStyle.name}</div>
</a>
))}
</div>

{/* Pagination */}
<div className="text-center mt-10">
<div className="join">
{paginationItems.map((page, index) => (
<button
<button
key={index}
className={`join-item btn ${page === '...' ? 'btn-disabled' : ''} ${page === currentPage.toString() ? 'btn-active' : ''}`}
onClick={() => handlePageChange(page)}
Expand Down
14 changes: 6 additions & 8 deletions web/src/components/client/ImageSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ type Image = {
interface ImageSliderProps {
images: Image[];
className?: string;
height: number;
heightClassName?: string;
}

const ImageSlider: React.FC<ImageSliderProps> = ({ images, className, height }) => {
if (className === undefined) className = "";
const ImageSlider: React.FC<ImageSliderProps> = ({ images, className = "", heightClassName = "h-[250px] lg:h-[700px]" }) => {
return (
<div className={`${className} bg-black`}>
<Swiper
Expand All @@ -42,11 +41,10 @@ const ImageSlider: React.FC<ImageSliderProps> = ({ images, className, height })
<div className="absolute left-0 top-0 z-10 bg-black bg-opacity-50 p-2 text-sm text-white">
{image.title}
</div>
<img
src={image.url}
alt={image.title}
className="block w-full object-contain"
style={{height: `${height}px`}}
<img
src={image.url}
alt={image.title}
className={`block w-full object-contain ${heightClassName}`}
/>
</div>
</SwiperSlide>
Expand Down
Loading
Loading