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:
@@ -11,9 +11,11 @@ import {
|
||||
Plus,
|
||||
FileText,
|
||||
Edit,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { TableRow, TableCell } from '@/components/ui/table';
|
||||
import { format } from 'date-fns';
|
||||
@@ -434,6 +436,11 @@ export function AttendanceManagement() {
|
||||
|
||||
computeStats: () => statCards,
|
||||
|
||||
// 검색창 (공통 컴포넌트에서 자동 생성)
|
||||
hideSearch: true,
|
||||
searchValue: searchValue,
|
||||
onSearchChange: setSearchValue,
|
||||
|
||||
dateRangeSelector: {
|
||||
enabled: true,
|
||||
showPresets: true,
|
||||
@@ -482,6 +489,7 @@ export function AttendanceManagement() {
|
||||
},
|
||||
|
||||
customFilterFn: (items, filterValues) => {
|
||||
if (!items || items.length === 0) return items;
|
||||
let filtered = items;
|
||||
const filterOption = filterValues.filter as string;
|
||||
if (filterOption && filterOption !== 'all') {
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { CreditCard, Edit, Trash2, Plus } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { CreditCard, Edit, Trash2, Plus, Search, RefreshCw } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { TableRow, TableCell } from '@/components/ui/table';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
@@ -58,7 +59,7 @@ export function CardManagement({ initialData }: CardManagementProps) {
|
||||
};
|
||||
|
||||
// 검색 및 필터 상태
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeTab, setActiveTab] = useState<string>('all');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 20;
|
||||
@@ -78,8 +79,8 @@ export function CardManagement({ initialData }: CardManagementProps) {
|
||||
}
|
||||
|
||||
// 검색 필터
|
||||
if (searchValue) {
|
||||
const search = searchValue.toLowerCase();
|
||||
if (searchQuery) {
|
||||
const search = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(c =>
|
||||
c.cardName.toLowerCase().includes(search) ||
|
||||
c.cardNumber.includes(search) ||
|
||||
@@ -89,7 +90,7 @@ export function CardManagement({ initialData }: CardManagementProps) {
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [cards, activeTab, searchValue]);
|
||||
}, [cards, activeTab, searchQuery]);
|
||||
|
||||
// 페이지네이션된 데이터
|
||||
const paginatedData = useMemo(() => {
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Users, Edit, Trash2, UserCheck, UserX, Clock, Calendar, Mail, Plus, Upload, Loader2 } from 'lucide-react';
|
||||
import { Users, Edit, Trash2, UserCheck, UserX, Clock, Calendar, Mail, Plus, Upload, Loader2, Search } from 'lucide-react';
|
||||
import { getEmployees, deleteEmployee, deleteEmployees, getEmployeeStats } from './actions';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { TableRow, TableCell } from '@/components/ui/table';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
@@ -437,6 +438,11 @@ export function EmployeeManagement() {
|
||||
|
||||
computeStats: () => statCards,
|
||||
|
||||
// 검색창 (공통 컴포넌트에서 자동 생성)
|
||||
hideSearch: true,
|
||||
searchValue: searchValue,
|
||||
onSearchChange: setSearchValue,
|
||||
|
||||
dateRangeSelector: {
|
||||
enabled: true,
|
||||
showPresets: true,
|
||||
@@ -489,6 +495,7 @@ export function EmployeeManagement() {
|
||||
},
|
||||
|
||||
customFilterFn: (items, filterValues) => {
|
||||
if (!items || items.length === 0) return items;
|
||||
let filtered = items;
|
||||
const filterOption = filterValues.filter as FilterOption;
|
||||
if (filterOption && filterOption !== 'all') {
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
Gift,
|
||||
MinusCircle,
|
||||
Loader2,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { TableRow, TableCell } from '@/components/ui/table';
|
||||
@@ -374,6 +376,11 @@ export function SalaryManagement() {
|
||||
|
||||
itemsPerPage: itemsPerPage,
|
||||
|
||||
// 검색창 (공통 컴포넌트에서 자동 생성)
|
||||
hideSearch: true,
|
||||
searchValue: searchQuery,
|
||||
onSearchChange: setSearchQuery,
|
||||
|
||||
// 날짜 범위 선택 (DateRangeSelector 사용)
|
||||
dateRangeSelector: {
|
||||
enabled: true,
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
type FilterFieldConfig,
|
||||
type FilterValues,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { VacationGrantDialog } from './VacationGrantDialog';
|
||||
import { VacationRequestDialog } from './VacationRequestDialog';
|
||||
@@ -587,48 +586,40 @@ export function VacationManagement() {
|
||||
}
|
||||
}, [mainTab, handleApproveClick, handleRejectClick]);
|
||||
|
||||
// ===== 헤더 액션 (DateRangeSelector + 버튼들) =====
|
||||
// ===== 헤더 액션 (탭별 버튼들만 - DateRangeSelector와 검색창은 공통 옵션 사용) =====
|
||||
const headerActions = useCallback(({ selectedItems: selected }: { selectedItems: Set<string>; onClearSelection?: () => void; onRefresh?: () => void }) => (
|
||||
<>
|
||||
<DateRangeSelector
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
/>
|
||||
<div className="ml-auto flex gap-2">
|
||||
{/* 탭별 액션 버튼 */}
|
||||
{mainTab === 'grant' && (
|
||||
<Button onClick={() => setGrantDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
부여등록
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 탭별 액션 버튼 */}
|
||||
{mainTab === 'grant' && (
|
||||
<Button onClick={() => setGrantDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
부여등록
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{mainTab === 'request' && (
|
||||
<>
|
||||
{/* 버튼 순서: 승인 → 거절 → 휴가신청 (휴가신청 버튼 위치 고정) */}
|
||||
{selected.size > 0 && (
|
||||
<>
|
||||
<Button variant="default" onClick={() => handleApproveClick(selected)}>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
승인
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => handleRejectClick(selected)}>
|
||||
<X className="h-4 w-4 mr-2" />
|
||||
거절
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={() => setRequestDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
휴가신청
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
), [startDate, endDate, mainTab, handleApproveClick, handleRejectClick]);
|
||||
{mainTab === 'request' && (
|
||||
<>
|
||||
{/* 버튼 순서: 승인 → 거절 → 휴가신청 (휴가신청 버튼 위치 고정) */}
|
||||
{selected.size > 0 && (
|
||||
<>
|
||||
<Button variant="default" onClick={() => handleApproveClick(selected)}>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
승인
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => handleRejectClick(selected)}>
|
||||
<X className="h-4 w-4 mr-2" />
|
||||
거절
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={() => setRequestDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
휴가신청
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
), [mainTab, handleApproveClick, handleRejectClick]);
|
||||
|
||||
// ===== filterConfig 기반 통합 필터 시스템 =====
|
||||
const filterConfig: FilterFieldConfig[] = useMemo(() => [
|
||||
@@ -693,6 +684,15 @@ export function VacationManagement() {
|
||||
|
||||
columns: tableColumns,
|
||||
|
||||
// 공통 패턴: dateRangeSelector
|
||||
dateRangeSelector: {
|
||||
enabled: true,
|
||||
startDate,
|
||||
endDate,
|
||||
onStartDateChange: setStartDate,
|
||||
onEndDateChange: setEndDate,
|
||||
},
|
||||
|
||||
tabs: tabs,
|
||||
defaultTab: mainTab,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user