feat(WEB): 리스트 페이지 UI 레이아웃 표준화
- 공통 레이아웃 패턴 적용: [달력] → [프리셋] → [검색창] → [버튼들] - beforeTableContent → headerActions + createButton 마이그레이션 - DateRangeSelector extraActions prop 활용하여 검색창 통합 - PricingListClient 테이블 행 클릭 → 상세 이동 기능 추가 - 회계 관련 페이지 (입금/출금/매입/매출/어음/카드/예상지출 등) 정리 - 건설 관련 페이지 검색 영역 정리 - 부모 메뉴 리다이렉트 컴포넌트 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,8 @@ interface DateRangeSelectorProps {
|
||||
hideDateInputs?: boolean;
|
||||
/** 날짜 입력 너비 */
|
||||
dateInputWidth?: string;
|
||||
/** 프리셋 버튼 위치: 'inline' (날짜 옆), 'below' (별도 줄) */
|
||||
presetsPosition?: 'inline' | 'below';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,7 @@ export function DateRangeSelector({
|
||||
hidePresets = false,
|
||||
hideDateInputs = false,
|
||||
dateInputWidth = 'w-[140px]',
|
||||
presetsPosition = 'inline',
|
||||
}: DateRangeSelectorProps) {
|
||||
|
||||
// 프리셋 클릭 핸들러
|
||||
@@ -119,59 +122,94 @@ export function DateRangeSelector({
|
||||
}
|
||||
}, [onStartDateChange, onEndDateChange]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{/* 1줄: 날짜 + 프리셋 */}
|
||||
{/* 태블릿/모바일(~1279px): 세로 배치 / PC(1280px+): 가로 한 줄 */}
|
||||
<div className="flex flex-col xl:flex-row xl:items-center gap-2">
|
||||
{/* 날짜 범위 선택 (Input type="date") */}
|
||||
{!hideDateInputs && (
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => onStartDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">~</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => onEndDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 기간 버튼들 - 모바일에서 가로 스크롤 */}
|
||||
{!hidePresets && presets.length > 0 && (
|
||||
<div
|
||||
className="overflow-x-auto -mx-1 px-1 xl:overflow-visible xl:mx-0 xl:px-0"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
<div className="flex items-center gap-1 min-w-max [&::-webkit-scrollbar]:hidden">
|
||||
{presets.map((preset) => (
|
||||
<Button
|
||||
key={preset}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePresetClick(preset)}
|
||||
className="shrink-0 text-xs sm:text-sm px-2 sm:px-3"
|
||||
>
|
||||
{PRESET_LABELS[preset]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
// 프리셋 버튼 렌더링
|
||||
const renderPresets = () => {
|
||||
if (hidePresets || presets.length === 0) return null;
|
||||
return (
|
||||
<div
|
||||
className="overflow-x-auto -mx-1 px-1 xl:overflow-visible xl:mx-0 xl:px-0"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
<div className="flex items-center gap-1 min-w-max [&::-webkit-scrollbar]:hidden">
|
||||
{presets.map((preset) => (
|
||||
<Button
|
||||
key={preset}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePresetClick(preset)}
|
||||
className="shrink-0 text-xs sm:text-sm px-2 sm:px-3"
|
||||
>
|
||||
{PRESET_LABELS[preset]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{/* 2줄: 추가 액션 버튼들 - 항상 별도 줄, 오른쪽 정렬 */}
|
||||
{extraActions && (
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
// presetsPosition이 'below'일 때: 달력+extraActions 같은 줄, 프리셋은 아래 줄
|
||||
if (presetsPosition === 'below') {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{/* 1줄: 날짜 + extraActions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 날짜 범위 선택 */}
|
||||
{!hideDateInputs && (
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => onStartDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">~</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => onEndDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* extraActions (검색창 등) */}
|
||||
{extraActions}
|
||||
</div>
|
||||
|
||||
{/* 2줄: 프리셋 버튼들 */}
|
||||
{renderPresets()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// presetsPosition이 'inline' (기본값)
|
||||
// PC(1280px+): 달력 | 프리셋버튼 | 검색창 (한 줄)
|
||||
// 태블릿: 달력 / 프리셋버튼 / 검색창 (세 줄)
|
||||
return (
|
||||
<div className="flex flex-col xl:flex-row xl:items-center gap-2 w-full">
|
||||
{/* 날짜 범위 선택 */}
|
||||
{!hideDateInputs && (
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => onStartDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">~</span>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => onEndDateChange(e.target.value)}
|
||||
className="w-[165px]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 기간 버튼들 - 달력 바로 옆 */}
|
||||
{renderPresets()}
|
||||
|
||||
{/* extraActions (검색창 등) - 마지막에 배치 */}
|
||||
{extraActions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user