feat(WEB): Pretendard 폰트 적용 및 HR/회계 모바일 필터 마이그레이션

- Pretendard Variable 폰트 추가 및 전역 적용
- HR 모듈 모바일 필터 적용:
  - AttendanceManagement: MobileFilter 컴포넌트 적용
  - EmployeeManagement: MobileFilter 컴포넌트 적용
  - SalaryManagement: MobileFilter 컴포넌트 적용
  - VacationManagement: MobileFilter 컴포넌트 적용
- 회계 모듈:
  - VendorManagement: MobileFilter 컴포넌트 적용
- 전자결재:
  - ReferenceBox: 모바일 UI 개선
- AuthenticatedLayout: 레이아웃 개선
- middleware: 설정 업데이트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-14 13:46:56 +09:00
parent ea8d701a8d
commit b08366c3f7
14 changed files with 881 additions and 239 deletions

View File

@@ -22,17 +22,12 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { TableRow, TableCell } from '@/components/ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
IntegratedListTemplateV2,
type TableColumn,
type StatCard,
type FilterFieldConfig,
type FilterValues,
} from '@/components/templates/IntegratedListTemplateV2';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import type {
@@ -370,80 +365,96 @@ export function VendorManagement({ initialData, initialTotal }: VendorManagement
);
}, [handleRowClick, handleEdit, handleDeleteClick]);
// ===== 테이블 헤더 액션 (5개 필터) =====
const tableHeaderActions = (
<div className="flex items-center gap-2 flex-wrap">
{/* 구분 필터 */}
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="구분" />
</SelectTrigger>
<SelectContent>
{VENDOR_CATEGORY_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
// ===== filterConfig 방식 모바일 필터 =====
const filterConfig: FilterFieldConfig[] = useMemo(() => [
{
key: 'category',
label: '구분',
type: 'single',
options: VENDOR_CATEGORY_OPTIONS.filter(o => o.value !== 'all').map(o => ({
value: o.value,
label: o.label,
})),
allOptionLabel: '전체',
},
{
key: 'creditRating',
label: '신용등급',
type: 'single',
options: CREDIT_RATING_OPTIONS.filter(o => o.value !== 'all').map(o => ({
value: o.value,
label: o.label,
})),
allOptionLabel: '전체',
},
{
key: 'transactionGrade',
label: '거래등급',
type: 'single',
options: TRANSACTION_GRADE_OPTIONS.filter(o => o.value !== 'all').map(o => ({
value: o.value,
label: o.label,
})),
allOptionLabel: '전체',
},
{
key: 'badDebt',
label: '악성채권',
type: 'single',
options: BAD_DEBT_STATUS_OPTIONS.filter(o => o.value !== 'all').map(o => ({
value: o.value,
label: o.label,
})),
allOptionLabel: '전체',
},
{
key: 'sort',
label: '정렬',
type: 'single',
options: SORT_OPTIONS.map(o => ({
value: o.value,
label: o.label,
})),
},
], []);
{/* 신용등급 필터 */}
<Select value={creditRatingFilter} onValueChange={setCreditRatingFilter}>
<SelectTrigger className="w-[110px]">
<SelectValue placeholder="신용등급" />
</SelectTrigger>
<SelectContent>
{CREDIT_RATING_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
const filterValues: FilterValues = useMemo(() => ({
category: categoryFilter,
creditRating: creditRatingFilter,
transactionGrade: transactionGradeFilter,
badDebt: badDebtFilter,
sort: sortOption,
}), [categoryFilter, creditRatingFilter, transactionGradeFilter, badDebtFilter, sortOption]);
{/* 거래등급 필터 */}
<Select value={transactionGradeFilter} onValueChange={setTransactionGradeFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="거래등급" />
</SelectTrigger>
<SelectContent>
{TRANSACTION_GRADE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
const handleFilterChange = useCallback((key: string, value: string | string[]) => {
switch (key) {
case 'category':
setCategoryFilter(value as string);
break;
case 'creditRating':
setCreditRatingFilter(value as string);
break;
case 'transactionGrade':
setTransactionGradeFilter(value as string);
break;
case 'badDebt':
setBadDebtFilter(value as string);
break;
case 'sort':
setSortOption(value as SortOption);
break;
}
setCurrentPage(1);
}, []);
{/* 악성채권 필터 */}
<Select value={badDebtFilter} onValueChange={setBadDebtFilter}>
<SelectTrigger className="w-[110px]">
<SelectValue placeholder="악성채권" />
</SelectTrigger>
<SelectContent>
{BAD_DEBT_STATUS_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
{/* 정렬 */}
<Select value={sortOption} onValueChange={(value) => setSortOption(value as SortOption)}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="정렬" />
</SelectTrigger>
<SelectContent>
{SORT_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
const handleFilterReset = useCallback(() => {
setCategoryFilter('all');
setCreditRatingFilter('all');
setTransactionGradeFilter('all');
setBadDebtFilter('all');
setSortOption('latest');
setCurrentPage(1);
}, []);
return (
<>