feat(WEB): 입력 컴포넌트 공통화 및 UI 개선

- 숫자/통화/전화번호/사업자번호 등 특수 입력 컴포넌트 추가
- MobileCard 컴포넌트 통합 (ListMobileCard 제거)
- IntegratedListTemplateV2 페이지네이션 버그 수정 (NaN 이슈)
- IntegratedDetailTemplate 타이틀 중복 수정
- 문서 시스템 컴포넌트 추가
- 헤더 벨 아이콘 포커스 스타일 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-21 20:56:17 +09:00
parent cfa72fe19b
commit 835c06ce94
190 changed files with 8575 additions and 2354 deletions

View File

@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { AccountNumberInput } from '@/components/ui/account-number-input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Select,
@@ -282,10 +283,10 @@ export function AccountDetail({ account, mode: initialMode }: AccountDetailProps
<div className="space-y-2">
<Label htmlFor="accountNumber"></Label>
<Input
<AccountNumberInput
id="accountNumber"
value={formData.accountNumber}
onChange={(e) => handleChange('accountNumber', e.target.value)}
onChange={(value) => handleChange('accountNumber', value)}
placeholder="1234-1234-1234-1234"
disabled={isEditMode} // 수정 시 계좌번호는 변경 불가
/>

View File

@@ -31,9 +31,9 @@ export const accountConfig: DetailConfig<Account> = {
{
key: 'accountNumber',
label: '계좌번호',
type: 'text',
type: 'accountNumber',
required: true,
placeholder: '1234-1234-1234-1234',
placeholder: '0000-0000-0000-0000',
// 수정 모드에서 비활성화 (계좌번호 변경 불가)
disabled: (mode) => mode === 'edit',
},

View File

@@ -39,7 +39,7 @@ import {
type RowClickHandlers,
type ListParams,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import type { Account } from './types';
import {
BANK_LABELS,

View File

@@ -6,6 +6,8 @@ import { Building2, Plus, Save, Upload, X, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { BusinessNumberInput } from '@/components/ui/business-number-input';
import { AccountNumberInput } from '@/components/ui/account-number-input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Select,
@@ -456,10 +458,10 @@ export function CompanyInfoManagement() {
</div>
<div className="space-y-2">
<Label htmlFor="businessNumber"></Label>
<Input
<BusinessNumberInput
id="businessNumber"
value={formData.businessNumber}
onChange={(e) => handleChange('businessNumber', e.target.value)}
onChange={(value) => handleChange('businessNumber', value)}
placeholder="123-12-12345"
disabled={!isEditMode}
/>
@@ -488,11 +490,11 @@ export function CompanyInfoManagement() {
</div>
<div className="space-y-2">
<Label htmlFor="paymentAccount"></Label>
<Input
<AccountNumberInput
id="paymentAccount"
value={formData.paymentAccount}
onChange={(e) => handleChange('paymentAccount', e.target.value)}
placeholder="123-1231-23-123"
onChange={(value) => handleChange('paymentAccount', value)}
placeholder="0000-0000-0000-0000"
disabled={!isEditMode}
/>
</div>

View File

@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { QuantityInput } from '@/components/ui/quantity-input';
import { Card, CardContent } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import {
@@ -198,12 +199,11 @@ export function LeavePolicyManagement() {
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-2">
<Input
type="number"
<QuantityInput
min={0}
max={100}
value={settings.defaultAnnualLeave}
onChange={(e) => updateField('defaultAnnualLeave', parseInt(e.target.value) || 0)}
onChange={(value) => updateField('defaultAnnualLeave', value ?? 0)}
className="w-20"
/>
<span className="text-sm text-muted-foreground"></span>
@@ -214,12 +214,11 @@ export function LeavePolicyManagement() {
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-2">
<Input
type="number"
<QuantityInput
min={0}
max={10}
value={settings.additionalLeavePerYear}
onChange={(e) => updateField('additionalLeavePerYear', parseInt(e.target.value) || 0)}
onChange={(value) => updateField('additionalLeavePerYear', value ?? 0)}
className="w-20"
/>
<span className="text-sm text-muted-foreground"></span>
@@ -230,12 +229,11 @@ export function LeavePolicyManagement() {
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-2">
<Input
type="number"
<QuantityInput
min={0}
max={100}
value={settings.maxAnnualLeave}
onChange={(e) => updateField('maxAnnualLeave', parseInt(e.target.value) || 0)}
onChange={(value) => updateField('maxAnnualLeave', value ?? 0)}
className="w-20"
/>
<span className="text-sm text-muted-foreground"></span>
@@ -276,12 +274,11 @@ export function LeavePolicyManagement() {
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-2">
<Input
type="number"
<QuantityInput
min={0}
max={100}
value={settings.carryOverMaxDays}
onChange={(e) => updateField('carryOverMaxDays', parseInt(e.target.value) || 0)}
onChange={(value) => updateField('carryOverMaxDays', value ?? 0)}
className="w-20"
/>
<span className="text-sm text-muted-foreground"></span>
@@ -292,12 +289,11 @@ export function LeavePolicyManagement() {
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-2">
<Input
type="number"
<QuantityInput
min={0}
max={24}
value={settings.carryOverExpiryMonths}
onChange={(e) => updateField('carryOverExpiryMonths', parseInt(e.target.value) || 0)}
onChange={(value) => updateField('carryOverExpiryMonths', value ?? 0)}
className="w-20"
/>
<span className="text-sm text-muted-foreground"></span>

View File

@@ -27,7 +27,7 @@ import {
type RowClickHandlers,
type ListParams,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import { getPayments } from './actions';
import type { PaymentHistory, SortOption } from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';

View File

@@ -17,7 +17,7 @@ import {
type UniversalListConfig,
type TableColumn,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import type { PaymentHistory, SortOption } from './types';
interface PaymentHistoryManagementProps {

View File

@@ -26,7 +26,7 @@ import {
type StatCard,
type TabOption,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import {
AlertDialog,
AlertDialogAction,

View File

@@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { TimePicker } from '@/components/ui/time-picker';
import { Label } from '@/components/ui/label';
import { QuantityInput } from '@/components/ui/quantity-input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import {
@@ -261,14 +262,13 @@ export function WorkScheduleManagement() {
<div className="space-y-2">
<Label htmlFor="weekly-hours"> </Label>
<div className="flex items-center gap-2">
<Input
<QuantityInput
id="weekly-hours"
type="number"
min={0}
max={52}
value={settings.weeklyWorkHours}
onChange={(e) =>
setSettings(prev => ({ ...prev, weeklyWorkHours: parseInt(e.target.value) || 0 }))
onChange={(value) =>
setSettings(prev => ({ ...prev, weeklyWorkHours: value ?? 0 }))
}
className="w-24"
/>
@@ -278,14 +278,13 @@ export function WorkScheduleManagement() {
<div className="space-y-2">
<Label htmlFor="overtime-hours"> </Label>
<div className="flex items-center gap-2">
<Input
<QuantityInput
id="overtime-hours"
type="number"
min={0}
max={52}
value={settings.weeklyOvertimeHours}
onChange={(e) =>
setSettings(prev => ({ ...prev, weeklyOvertimeHours: parseInt(e.target.value) || 0 }))
onChange={(value) =>
setSettings(prev => ({ ...prev, weeklyOvertimeHours: value ?? 0 }))
}
className="w-24"
/>