feat(WEB): 입력 컴포넌트 공통화 및 UI 개선
- 숫자/통화/전화번호/사업자번호 등 특수 입력 컴포넌트 추가 - MobileCard 컴포넌트 통합 (ListMobileCard 제거) - IntegratedListTemplateV2 페이지네이션 버그 수정 (NaN 이슈) - IntegratedDetailTemplate 타이틀 중복 수정 - 문서 시스템 컴포넌트 추가 - 헤더 벨 아이콘 포커스 스타일 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
type FilterValues,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { AttendanceInfoDialog } from './AttendanceInfoDialog';
|
||||
import { ReasonInfoDialog } from './ReasonInfoDialog';
|
||||
import {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { TableRow, TableCell } from '@/components/ui/table';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import {
|
||||
UniversalListPage,
|
||||
type UniversalListConfig,
|
||||
|
||||
@@ -56,9 +56,9 @@ export const cardConfig: DetailConfig<Card> = {
|
||||
{
|
||||
key: 'cardNumber',
|
||||
label: '카드번호',
|
||||
type: 'text',
|
||||
type: 'cardNumber',
|
||||
required: true,
|
||||
placeholder: '1234-1234-1234-1234',
|
||||
placeholder: '0000-0000-0000-0000',
|
||||
helpText: '16자리 카드번호를 입력하세요',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
type UniversalListConfig,
|
||||
type TabOption,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { toast } from 'sonner';
|
||||
import type { Card } from './types';
|
||||
import {
|
||||
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { CurrencyInput } from '@/components/ui/currency-input';
|
||||
import { PhoneInput } from '@/components/ui/phone-input';
|
||||
import { PersonalNumberInput } from '@/components/ui/personal-number-input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -189,10 +192,10 @@ export function EmployeeDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="residentNumber">주민등록번호</Label>
|
||||
<Input
|
||||
<PersonalNumberInput
|
||||
id="residentNumber"
|
||||
value={formData.residentNumber}
|
||||
onChange={(e) => handleChange('residentNumber', e.target.value)}
|
||||
onChange={(value) => handleChange('residentNumber', value)}
|
||||
disabled={isViewMode}
|
||||
placeholder="000000-0000000"
|
||||
/>
|
||||
@@ -200,10 +203,10 @@ export function EmployeeDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">휴대폰</Label>
|
||||
<Input
|
||||
<PhoneInput
|
||||
id="phone"
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleChange('phone', e.target.value)}
|
||||
onChange={(value) => handleChange('phone', value)}
|
||||
disabled={isViewMode}
|
||||
placeholder="010-0000-0000"
|
||||
/>
|
||||
@@ -223,13 +226,12 @@ export function EmployeeDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="salary">연봉</Label>
|
||||
<Input
|
||||
<CurrencyInput
|
||||
id="salary"
|
||||
type="number"
|
||||
value={formData.salary}
|
||||
onChange={(e) => handleChange('salary', e.target.value)}
|
||||
value={formData.salary ? Number(formData.salary) : undefined}
|
||||
onChange={(value) => handleChange('salary', value?.toString() ?? '')}
|
||||
disabled={isViewMode}
|
||||
placeholder="연봉 (원)"
|
||||
placeholder="연봉"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,9 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { CurrencyInput } from '@/components/ui/currency-input';
|
||||
import { PhoneInput } from '@/components/ui/phone-input';
|
||||
import { PersonalNumberInput } from '@/components/ui/personal-number-input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -403,10 +406,10 @@ export function EmployeeForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="residentNumber">주민등록번호</Label>
|
||||
<Input
|
||||
<PersonalNumberInput
|
||||
id="residentNumber"
|
||||
value={formData.residentNumber}
|
||||
onChange={(e) => handleChange('residentNumber', e.target.value)}
|
||||
onChange={(value) => handleChange('residentNumber', value)}
|
||||
placeholder="000000-0000000"
|
||||
disabled={isViewMode}
|
||||
/>
|
||||
@@ -414,10 +417,10 @@ export function EmployeeForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">휴대폰</Label>
|
||||
<Input
|
||||
<PhoneInput
|
||||
id="phone"
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleChange('phone', e.target.value)}
|
||||
onChange={(value) => handleChange('phone', value)}
|
||||
placeholder="010-0000-0000"
|
||||
disabled={isViewMode}
|
||||
/>
|
||||
@@ -439,12 +442,11 @@ export function EmployeeForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="salary">연봉</Label>
|
||||
<Input
|
||||
<CurrencyInput
|
||||
id="salary"
|
||||
type="number"
|
||||
value={formData.salary}
|
||||
onChange={(e) => handleChange('salary', e.target.value)}
|
||||
placeholder="연봉 (원)"
|
||||
value={formData.salary ? Number(formData.salary) : undefined}
|
||||
onChange={(value) => handleChange('salary', value?.toString() ?? '')}
|
||||
placeholder="연봉"
|
||||
disabled={isViewMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { DetailConfig } from '@/components/templates/IntegratedDetailTempla
|
||||
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
|
||||
*/
|
||||
export const employeeCreateConfig: DetailConfig = {
|
||||
title: '사원 등록',
|
||||
title: '사원',
|
||||
description: '새로운 사원을 등록합니다',
|
||||
icon: Users,
|
||||
basePath: '/hr/employee-management',
|
||||
@@ -27,7 +27,7 @@ export const employeeCreateConfig: DetailConfig = {
|
||||
*/
|
||||
export const employeeEditConfig: DetailConfig = {
|
||||
...employeeCreateConfig,
|
||||
title: '사원 수정',
|
||||
title: '사원',
|
||||
description: '사원 정보를 수정합니다',
|
||||
actions: {
|
||||
...employeeCreateConfig.actions,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
type FilterValues,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { FieldSettingsDialog } from './FieldSettingsDialog';
|
||||
import { UserInviteDialog } from './UserInviteDialog';
|
||||
import type {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { CurrencyInput } from '@/components/ui/currency-input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -263,15 +264,11 @@ export function SalaryDetailDialog({
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">직책수당</span>
|
||||
{isEditingAllowances ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
value={editedAllowances.positionAllowance}
|
||||
onChange={(e) => handleAllowanceChange('positionAllowance', e.target.value)}
|
||||
className="w-28 h-7 text-right text-sm"
|
||||
/>
|
||||
<span className="text-xs">원</span>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
value={editedAllowances.positionAllowance}
|
||||
onChange={(value) => handleAllowanceChange('positionAllowance', String(value ?? 0))}
|
||||
className="w-32 h-7 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<span>{formatCurrency(editedAllowances.positionAllowance)}원</span>
|
||||
)}
|
||||
@@ -279,15 +276,11 @@ export function SalaryDetailDialog({
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">초과근무수당</span>
|
||||
{isEditingAllowances ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
value={editedAllowances.overtimeAllowance}
|
||||
onChange={(e) => handleAllowanceChange('overtimeAllowance', e.target.value)}
|
||||
className="w-28 h-7 text-right text-sm"
|
||||
/>
|
||||
<span className="text-xs">원</span>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
value={editedAllowances.overtimeAllowance}
|
||||
onChange={(value) => handleAllowanceChange('overtimeAllowance', String(value ?? 0))}
|
||||
className="w-32 h-7 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<span>{formatCurrency(editedAllowances.overtimeAllowance)}원</span>
|
||||
)}
|
||||
@@ -295,15 +288,11 @@ export function SalaryDetailDialog({
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">식대</span>
|
||||
{isEditingAllowances ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
value={editedAllowances.mealAllowance}
|
||||
onChange={(e) => handleAllowanceChange('mealAllowance', e.target.value)}
|
||||
className="w-28 h-7 text-right text-sm"
|
||||
/>
|
||||
<span className="text-xs">원</span>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
value={editedAllowances.mealAllowance}
|
||||
onChange={(value) => handleAllowanceChange('mealAllowance', String(value ?? 0))}
|
||||
className="w-32 h-7 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<span>{formatCurrency(editedAllowances.mealAllowance)}원</span>
|
||||
)}
|
||||
@@ -311,15 +300,11 @@ export function SalaryDetailDialog({
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">교통비</span>
|
||||
{isEditingAllowances ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
value={editedAllowances.transportAllowance}
|
||||
onChange={(e) => handleAllowanceChange('transportAllowance', e.target.value)}
|
||||
className="w-28 h-7 text-right text-sm"
|
||||
/>
|
||||
<span className="text-xs">원</span>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
value={editedAllowances.transportAllowance}
|
||||
onChange={(value) => handleAllowanceChange('transportAllowance', String(value ?? 0))}
|
||||
className="w-32 h-7 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<span>{formatCurrency(editedAllowances.transportAllowance)}원</span>
|
||||
)}
|
||||
@@ -327,15 +312,11 @@ export function SalaryDetailDialog({
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">기타수당</span>
|
||||
{isEditingAllowances ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
value={editedAllowances.otherAllowance}
|
||||
onChange={(e) => handleAllowanceChange('otherAllowance', e.target.value)}
|
||||
className="w-28 h-7 text-right text-sm"
|
||||
/>
|
||||
<span className="text-xs">원</span>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
value={editedAllowances.otherAllowance}
|
||||
onChange={(value) => handleAllowanceChange('otherAllowance', String(value ?? 0))}
|
||||
className="w-32 h-7 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<span>{formatCurrency(editedAllowances.otherAllowance)}원</span>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
type FilterFieldConfig,
|
||||
type FilterValues,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { SalaryDetailDialog } from './SalaryDetailDialog';
|
||||
import {
|
||||
getSalaries,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { QuantityInput } from '@/components/ui/quantity-input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -166,12 +167,11 @@ export function VacationGrantDialog({
|
||||
{/* 부여 일수 */}
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="grantDays">부여 일수</Label>
|
||||
<Input
|
||||
<QuantityInput
|
||||
id="grantDays"
|
||||
type="number"
|
||||
min={1}
|
||||
value={formData.grantDays}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, grantDays: parseInt(e.target.value) || 1 }))}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, grantDays: value ?? 1 }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { NumberInput } from '@/components/ui/number-input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -158,15 +159,15 @@ export function VacationRegisterDialog({
|
||||
<Label htmlFor="days">
|
||||
지급 일수 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
<NumberInput
|
||||
id="days"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.5"
|
||||
value={formData.days || ''}
|
||||
onChange={(e) => setFormData((prev: VacationFormData) => ({ ...prev, days: parseFloat(e.target.value) || 0 }))}
|
||||
className={errors.days ? 'border-red-500' : ''}
|
||||
min={0}
|
||||
step={0.5}
|
||||
value={formData.days}
|
||||
onChange={(value) => setFormData((prev: VacationFormData) => ({ ...prev, days: value ?? 0 }))}
|
||||
error={!!errors.days}
|
||||
placeholder="일수를 입력하세요"
|
||||
allowDecimal
|
||||
/>
|
||||
{errors.days && (
|
||||
<p className="text-sm text-red-500">{errors.days}</p>
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
type FilterValues,
|
||||
} from '@/components/templates/UniversalListPage';
|
||||
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
|
||||
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { VacationGrantDialog } from './VacationGrantDialog';
|
||||
import { VacationRequestDialog } from './VacationRequestDialog';
|
||||
import type {
|
||||
|
||||
Reference in New Issue
Block a user