2026-01-05 18:59:04 +09:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { cn } from '@/components/ui/utils';
|
|
|
|
|
import type { CalendarHeaderProps, CalendarView } from './types';
|
|
|
|
|
import { formatYearMonth } from './utils';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 달력 헤더 컴포넌트
|
|
|
|
|
* - 년월 표시 및 네비게이션 (◀ ▶)
|
|
|
|
|
* - 주/월 뷰 전환 탭
|
|
|
|
|
* - 필터 slot (children으로 외부 주입)
|
|
|
|
|
*/
|
|
|
|
|
export function CalendarHeader({
|
|
|
|
|
currentDate,
|
|
|
|
|
view,
|
|
|
|
|
onPrevMonth,
|
|
|
|
|
onNextMonth,
|
|
|
|
|
onViewChange,
|
2026-01-06 09:58:10 +09:00
|
|
|
titleSlot,
|
2026-01-05 18:59:04 +09:00
|
|
|
filterSlot,
|
|
|
|
|
}: CalendarHeaderProps) {
|
|
|
|
|
const views: { value: CalendarView; label: string }[] = [
|
|
|
|
|
{ value: 'week', label: '주' },
|
|
|
|
|
{ value: 'month', label: '월' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-13 17:18:29 +09:00
|
|
|
<div className="flex flex-col gap-3 pb-3 border-b">
|
|
|
|
|
{/* PC: 타이틀 + 네비게이션 | 뷰전환 + 필터 (한 줄) */}
|
|
|
|
|
{/* 모바일: 타이틀 / 네비게이션 + 뷰전환 / 필터 (세 줄) */}
|
2026-01-05 18:59:04 +09:00
|
|
|
|
2026-01-13 17:18:29 +09:00
|
|
|
{/* 1줄(모바일) / 좌측(PC): 타이틀 */}
|
|
|
|
|
{titleSlot && (
|
|
|
|
|
<div className="xl:hidden text-base font-semibold text-foreground">
|
|
|
|
|
{titleSlot}
|
2026-01-06 09:58:10 +09:00
|
|
|
</div>
|
2026-01-13 17:18:29 +09:00
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 2줄(모바일) / 전체(PC): 네비게이션 + 뷰전환 + 필터 */}
|
|
|
|
|
<div className="flex flex-col xl:flex-row xl:items-center xl:justify-between gap-3">
|
|
|
|
|
{/* 좌측: (PC에서만 타이틀) + 네비게이션 */}
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
{titleSlot && (
|
|
|
|
|
<span className="hidden xl:block text-base font-semibold text-foreground">
|
|
|
|
|
{titleSlot}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-8 w-8 shrink-0 hover:bg-primary/10"
|
|
|
|
|
onClick={onPrevMonth}
|
|
|
|
|
>
|
|
|
|
|
<ChevronLeft className="h-4 w-4" />
|
|
|
|
|
</Button>
|
2026-01-05 18:59:04 +09:00
|
|
|
|
2026-01-13 17:18:29 +09:00
|
|
|
<span className="text-lg font-bold min-w-[120px] text-center">
|
|
|
|
|
{formatYearMonth(currentDate)}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-8 w-8 shrink-0 hover:bg-primary/10"
|
|
|
|
|
onClick={onNextMonth}
|
2026-01-05 18:59:04 +09:00
|
|
|
>
|
2026-01-13 17:18:29 +09:00
|
|
|
<ChevronRight className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 모바일: 뷰 전환 탭 (네비게이션 옆) */}
|
|
|
|
|
<div className="flex xl:hidden rounded-md border">
|
|
|
|
|
{views.map((v) => (
|
|
|
|
|
<button
|
|
|
|
|
key={v.value}
|
|
|
|
|
onClick={() => onViewChange(v.value)}
|
|
|
|
|
className={cn(
|
|
|
|
|
'px-4 py-1.5 text-sm font-medium transition-colors',
|
|
|
|
|
'first:rounded-l-md last:rounded-r-md',
|
|
|
|
|
view === v.value
|
|
|
|
|
? 'bg-primary text-primary-foreground'
|
|
|
|
|
: 'hover:bg-primary/10 text-foreground'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{v.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-05 18:59:04 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-01-13 17:18:29 +09:00
|
|
|
{/* 우측(PC만): 뷰 전환 + 필터 */}
|
|
|
|
|
<div className="hidden xl:flex items-center gap-3">
|
|
|
|
|
<div className="flex rounded-md border">
|
|
|
|
|
{views.map((v) => (
|
|
|
|
|
<button
|
|
|
|
|
key={v.value}
|
|
|
|
|
onClick={() => onViewChange(v.value)}
|
|
|
|
|
className={cn(
|
|
|
|
|
'px-4 py-1.5 text-sm font-medium transition-colors',
|
|
|
|
|
'first:rounded-l-md last:rounded-r-md',
|
|
|
|
|
view === v.value
|
|
|
|
|
? 'bg-primary text-primary-foreground'
|
|
|
|
|
: 'hover:bg-primary/10 text-foreground'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{v.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
{filterSlot && <div className="flex items-center gap-2">{filterSlot}</div>}
|
|
|
|
|
</div>
|
2026-01-05 18:59:04 +09:00
|
|
|
</div>
|
2026-01-13 17:18:29 +09:00
|
|
|
|
|
|
|
|
{/* 3줄(모바일만): 필터 */}
|
|
|
|
|
{filterSlot && (
|
|
|
|
|
<div className="flex xl:hidden items-center gap-2">{filterSlot}</div>
|
|
|
|
|
)}
|
2026-01-05 18:59:04 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|