1291 lines
74 KiB
TypeScript
Executable File
1291 lines
74 KiB
TypeScript
Executable File
'use client';
|
||
|
||
import * as React from 'react';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import { useForm } from 'react-hook-form';
|
||
import { z } from 'zod';
|
||
import dayjs from 'dayjs';
|
||
import 'dayjs/locale/zh-cn';
|
||
import { CalendarIcon, Plus, Trash2 } from 'lucide-react';
|
||
import { IconChevronDown, IconCheck, IconX } from '@tabler/icons-react';
|
||
|
||
import {
|
||
Sheet,
|
||
SheetContent,
|
||
SheetDescription,
|
||
SheetHeader,
|
||
SheetTitle,
|
||
SheetFooter,
|
||
} from '@nice/ui/components/sheet';
|
||
import { Button } from '@nice/ui/components/button';
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from '@nice/ui/components/form';
|
||
import { Input } from '@nice/ui/components/input';
|
||
import { Textarea } from '@nice/ui/components/textarea';
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '@nice/ui/components/select';
|
||
import { Calendar } from '@nice/ui/components/calendar';
|
||
import { Popover, PopoverContent, PopoverTrigger } from '@nice/ui/components/popover';
|
||
import { DateTimePicker } from '@nice/ui/components/date-time-picker';
|
||
import {
|
||
Command,
|
||
CommandEmpty,
|
||
CommandGroup,
|
||
CommandItem,
|
||
CommandList,
|
||
} from '@nice/ui/components/command';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@nice/ui/components/card';
|
||
import { cn } from '@nice/ui/lib/utils';
|
||
import { DeptSelect, DutySelect } from '@/components/selector';
|
||
import { AvatarUpload } from '@/components/common/avatar-upload';
|
||
import { Education, EducationForm, PoliticalStatus, TrainType, AppraisalLevel, ProfileMetadata } from '@fenghuo/common';
|
||
import { useProfile } from './profile-provider';
|
||
import { useTranslation } from '@nice/i18n';
|
||
|
||
|
||
// 定义表单验证模式
|
||
const createProfileFormSchema = (t: (key: string) => string) => z.object({
|
||
// 基本信息
|
||
name: z.string().min(1, t('profile.profile_sheet.validation.name_required')),
|
||
gender: z.number().min(1).max(2),
|
||
idNum: z.string().min(15, t('profile.profile_sheet.validation.id_number_min_length')),
|
||
paperId: z.string().min(1, t('profile.profile_sheet.validation.certificate_number_required')), // 改为必选
|
||
avatar: z.string().optional(), // 添加头像字段,可选
|
||
command: z.string(), // 添加编制命令字段
|
||
birthday: z.date().optional(), // 添加生日字段
|
||
|
||
// 入职信息 - 改为必选
|
||
hireDate: z.date({ required_error: t('profile.profile_sheet.validation.hire_date_required') }),
|
||
relativeHireDate: z.date().optional(),
|
||
|
||
// 身份信息 - 改为必选
|
||
identity: z.string().min(1, t('profile.profile_sheet.validation.identity_required')),
|
||
level: z.string().min(1, t('profile.profile_sheet.validation.rank_required')),
|
||
levelDate: z.date({ required_error: t('profile.profile_sheet.validation.rank_time_required') }),
|
||
|
||
// 职务信息 - 改为必选
|
||
dutyCode: z.string().optional(),
|
||
dutyLevel: z.string().optional(), // 改为非必选
|
||
dutyName: z.string().min(1, t('profile.profile_sheet.validation.duty_name_required')),
|
||
|
||
// 组织信息 - 改为必选
|
||
organizationId: z.string().min(1, t('profile.profile_sheet.validation.organization_required')),
|
||
|
||
// 元数据
|
||
metadata: z.object({
|
||
ethnicity: z.string().optional(),
|
||
bloodType: z.string().optional(),
|
||
proxyDuty: z.string().optional(),
|
||
isRehire: z.boolean().optional(),
|
||
isExtendedDeparture: z.boolean().optional(),
|
||
positionYear: z.number().optional(),
|
||
politicalStatus: z.string().optional(),
|
||
partyPosition: z.string().optional(),
|
||
native: z.object({
|
||
province: z.string().optional(),
|
||
city: z.string().optional(),
|
||
county: z.string().optional(),
|
||
}).optional(),
|
||
education: z.string().optional(),
|
||
educationForm: z.string().optional(),
|
||
isGraduate: z.boolean().optional(),
|
||
schoolMajor: z.string().optional(),
|
||
drills: z.number().optional(),
|
||
train: z.array(z.object({
|
||
type: z.string().optional(),
|
||
location: z.string().optional(),
|
||
major: z.string().optional(),
|
||
})).optional(),
|
||
appraisal: z.array(z.object({
|
||
level: z.string().optional(),
|
||
job: z.string().optional(),
|
||
})).optional(),
|
||
foreignLanguage: z.string().optional(),
|
||
reward: z.string().optional(),
|
||
punish: z.string().optional(),
|
||
remark: z.string().optional(),
|
||
}).optional(),
|
||
});
|
||
|
||
// 职务水平选择器组件
|
||
interface DutyLevelSelectProps {
|
||
value?: string;
|
||
onValueChange?: (value: string) => void;
|
||
placeholder?: string;
|
||
className?: string;
|
||
disabled?: boolean;
|
||
}
|
||
|
||
function DutyLevelSelect({
|
||
value,
|
||
onValueChange,
|
||
placeholder = '请选择职务水平',
|
||
className,
|
||
disabled = false,
|
||
}: DutyLevelSelectProps) {
|
||
const { t } = useTranslation();
|
||
const [open, setOpen] = React.useState(false);
|
||
|
||
// 职务水平选项
|
||
const dutyLevelOptions = [
|
||
{ value: '1', label: t('profile.profile_sheet.duty_level_primary') },
|
||
{ value: '2', label: t('profile.profile_sheet.duty_level_middle') },
|
||
{ value: '3', label: t('profile.profile_sheet.duty_level_high') },
|
||
];
|
||
|
||
// 获取当前选中的选项
|
||
const selectedOption = dutyLevelOptions.find(option => option.value === value);
|
||
|
||
// 处理选择逻辑
|
||
const handleSelect = (selectedValue: string) => {
|
||
if (!onValueChange) return;
|
||
|
||
// 如果点击的是当前已选中的值,则清空选择
|
||
if (value === selectedValue) {
|
||
onValueChange('');
|
||
} else {
|
||
onValueChange(selectedValue);
|
||
}
|
||
setOpen(false);
|
||
};
|
||
|
||
// 清除选择
|
||
const handleClear = (e: React.MouseEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
onValueChange?.('');
|
||
};
|
||
|
||
return (
|
||
<Popover open={open} onOpenChange={setOpen}>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
role="combobox"
|
||
aria-expanded={open}
|
||
className={cn(
|
||
'w-full justify-between text-left font-normal',
|
||
!selectedOption && 'text-muted-foreground',
|
||
className,
|
||
)}
|
||
disabled={disabled}
|
||
>
|
||
<span className="truncate">
|
||
{selectedOption ? selectedOption.label : placeholder}
|
||
</span>
|
||
<div className="flex items-center gap-1 shrink-0">
|
||
{selectedOption && (
|
||
<div
|
||
role="button"
|
||
tabIndex={0}
|
||
onClick={handleClear}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' || e.key === ' ') {
|
||
handleClear(e as any);
|
||
}
|
||
}}
|
||
className="flex items-center justify-center w-4 h-4 rounded-sm hover:bg-muted/50 text-muted-foreground hover:text-foreground transition-colors"
|
||
aria-label={t('common.clear_selection')}
|
||
>
|
||
<IconX className="h-3 w-3" />
|
||
</div>
|
||
)}
|
||
<IconChevronDown className="h-4 w-4 shrink-0 opacity-50" />
|
||
</div>
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-full p-0" style={{ width: 'var(--radix-popover-trigger-width)' }} align="start">
|
||
<Command>
|
||
<CommandList>
|
||
<CommandEmpty>{t('profile.profile_sheet.duty_level_not_found')}</CommandEmpty>
|
||
<CommandGroup>
|
||
{dutyLevelOptions.map((option) => (
|
||
<CommandItem
|
||
key={option.value}
|
||
value={option.value}
|
||
onSelect={() => handleSelect(option.value)}
|
||
className={cn(
|
||
'flex items-center justify-between cursor-pointer',
|
||
'hover:bg-accent hover:text-accent-foreground',
|
||
value === option.value && 'bg-accent text-accent-foreground',
|
||
)}
|
||
>
|
||
<span>{option.label}</span>
|
||
{value === option.value && (
|
||
<IconCheck className="h-4 w-4 text-primary" />
|
||
)}
|
||
</CommandItem>
|
||
))}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
);
|
||
}
|
||
|
||
|
||
export function ProfileSheet() {
|
||
const { t } = useTranslation();
|
||
const {
|
||
isSheetOpen,
|
||
setIsSheetOpen,
|
||
editingProfile,
|
||
sheetMode,
|
||
handleSubmitEmployee,
|
||
} = useProfile();
|
||
|
||
// 创建动态的表单验证模式
|
||
const profileFormSchema = React.useMemo(() => createProfileFormSchema(t), [t]);
|
||
|
||
type ProfileFormValues = z.infer<typeof profileFormSchema>;
|
||
|
||
const form = useForm<ProfileFormValues>({
|
||
resolver: zodResolver(profileFormSchema),
|
||
defaultValues: {
|
||
name: '',
|
||
gender: 1,
|
||
idNum: '',
|
||
paperId: '',
|
||
avatar: '',
|
||
command: '',
|
||
birthday: undefined,
|
||
hireDate: undefined, // 改为 undefined,保持一致性
|
||
relativeHireDate: undefined, // 改为 undefined,保持一致性
|
||
identity: '',
|
||
level: '',
|
||
levelDate: undefined, // 改为 undefined,保持一致性
|
||
dutyCode: '',
|
||
dutyLevel: '',
|
||
dutyName: '',
|
||
organizationId: '',
|
||
metadata: {
|
||
ethnicity: '',
|
||
bloodType: '',
|
||
proxyDuty: '',
|
||
isRehire: false,
|
||
isExtendedDeparture: false,
|
||
positionYear: undefined,
|
||
politicalStatus: '',
|
||
partyPosition: '',
|
||
native: {
|
||
province: '',
|
||
city: '',
|
||
county: '',
|
||
},
|
||
education: '',
|
||
educationForm: '',
|
||
isGraduate: false,
|
||
schoolMajor: '',
|
||
drills: undefined,
|
||
train: [{ type: '', location: '', major: '' }],
|
||
appraisal: [{ level: '', job: '' }],
|
||
foreignLanguage: '',
|
||
reward: '',
|
||
punish: '',
|
||
remark: '',
|
||
},
|
||
},
|
||
});
|
||
|
||
// 当编辑数据变化时,回显数据
|
||
React.useEffect(() => {
|
||
if (editingProfile && sheetMode === 'edit') {
|
||
const metadata = editingProfile.metadata as ProfileMetadata;
|
||
form.reset({
|
||
name: editingProfile.name || '',
|
||
gender: editingProfile.gender || 1,
|
||
idNum: editingProfile.idNum || '',
|
||
paperId: editingProfile.paperId || '',
|
||
avatar: editingProfile.avatar || '',
|
||
birthday: editingProfile.birthday ? new Date(editingProfile.birthday) : undefined,
|
||
command: editingProfile.command || '',
|
||
hireDate: editingProfile.hireDate ? new Date(editingProfile.hireDate) : undefined,
|
||
relativeHireDate: editingProfile.relativeHireDate ? new Date(editingProfile.relativeHireDate) : undefined,
|
||
identity: editingProfile.identity || '',
|
||
level: editingProfile.level || '',
|
||
levelDate: editingProfile.levelDate ? new Date(editingProfile.levelDate) : undefined,
|
||
dutyCode: editingProfile.dutyCode || '',
|
||
dutyLevel: editingProfile.dutyLevel?.toString() || '',
|
||
dutyName: editingProfile.dutyName || '',
|
||
organizationId: editingProfile.organizationId || '',
|
||
metadata: {
|
||
ethnicity: metadata?.ethnicity || '',
|
||
bloodType: metadata?.bloodType || '',
|
||
proxyDuty: metadata?.proxyDuty || '',
|
||
isRehire: metadata?.isRehire || false,
|
||
isExtendedDeparture: metadata?.isExtendedDeparture || false,
|
||
positionYear: metadata?.positionYear,
|
||
politicalStatus: metadata?.politicalStatus || '',
|
||
partyPosition: metadata?.partyPosition || '',
|
||
native: {
|
||
province: metadata?.native?.province || '',
|
||
city: metadata?.native?.city || '',
|
||
county: metadata?.native?.county || '',
|
||
},
|
||
education: metadata?.education || '',
|
||
educationForm: metadata?.educationForm || '',
|
||
isGraduate: metadata?.isGraduate || false,
|
||
schoolMajor: metadata?.schoolMajor || '',
|
||
drills: metadata?.drills,
|
||
train: metadata?.train || [{ type: '', location: '', major: '' }],
|
||
appraisal: metadata?.appraisal || [{ level: '', job: '' }],
|
||
foreignLanguage: metadata?.foreignLanguage || '',
|
||
reward: metadata?.reward || '',
|
||
punish: metadata?.punish || '',
|
||
remark: metadata?.remark || '',
|
||
},
|
||
});
|
||
} else if (sheetMode === 'create') {
|
||
// 创建模式时重置表单 - 保持与 defaultValues 一致
|
||
form.reset({
|
||
name: '',
|
||
gender: 1,
|
||
idNum: '',
|
||
paperId: '',
|
||
avatar: '',
|
||
birthday: undefined,
|
||
command: '',
|
||
hireDate: undefined,
|
||
relativeHireDate: undefined,
|
||
identity: '',
|
||
level: '',
|
||
levelDate: undefined,
|
||
dutyCode: '',
|
||
dutyLevel: '',
|
||
dutyName: '',
|
||
organizationId: '',
|
||
metadata: {
|
||
ethnicity: '',
|
||
bloodType: '',
|
||
proxyDuty: '',
|
||
isRehire: false,
|
||
isExtendedDeparture: false,
|
||
positionYear: undefined,
|
||
politicalStatus: '',
|
||
partyPosition: '',
|
||
native: {
|
||
province: '',
|
||
city: '',
|
||
county: '',
|
||
},
|
||
education: '',
|
||
educationForm: '',
|
||
isGraduate: false,
|
||
schoolMajor: '',
|
||
drills: undefined,
|
||
train: [{ type: '', location: '', major: '' }],
|
||
appraisal: [{ level: '', job: '' }],
|
||
foreignLanguage: '',
|
||
reward: '',
|
||
punish: '',
|
||
remark: '',
|
||
},
|
||
});
|
||
}
|
||
}, [editingProfile, sheetMode, form]);
|
||
|
||
// 处理 Sheet 开关状态变化
|
||
const handleOpenChange = (newOpen: boolean) => {
|
||
setIsSheetOpen(newOpen);
|
||
// 当 Sheet 关闭时清空表单
|
||
if (!newOpen) {
|
||
form.reset();
|
||
}
|
||
};
|
||
|
||
const handleSubmit = (data: ProfileFormValues) => {
|
||
handleSubmitEmployee(data);
|
||
setIsSheetOpen(false);
|
||
form.reset();
|
||
};
|
||
|
||
const addTrainExperience = () => {
|
||
const current = form.getValues('metadata.train') || [];
|
||
form.setValue('metadata.train', [...current, { type: '', location: '', major: '' }]);
|
||
};
|
||
|
||
const removeTrainExperience = (index: number) => {
|
||
const current = form.getValues('metadata.train') || [];
|
||
form.setValue('metadata.train', current.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const addAppraisalExperience = () => {
|
||
const current = form.getValues('metadata.appraisal') || [];
|
||
form.setValue('metadata.appraisal', [...current, { level: '', job: '' }]);
|
||
};
|
||
|
||
const removeAppraisalExperience = (index: number) => {
|
||
const current = form.getValues('metadata.appraisal') || [];
|
||
form.setValue('metadata.appraisal', current.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const politicalStatusOptions = Object.values(PoliticalStatus);
|
||
const educationFormOptions = Object.values(EducationForm);
|
||
const trainTypeOptions = Object.values(TrainType);
|
||
const appraisalLevelOptions = Object.values(AppraisalLevel);
|
||
const educationOptions = Object.values(Education);
|
||
|
||
return (
|
||
<Sheet open={isSheetOpen} onOpenChange={handleOpenChange}>
|
||
<SheetContent side="right" className="w-1/3 min-w-[600px] p-3 overflow-y-auto mb-2">
|
||
<SheetHeader>
|
||
<SheetTitle>
|
||
{sheetMode === 'edit'
|
||
? t('profile.profile_sheet.title_edit')
|
||
: t('profile.profile_sheet.title_add')
|
||
}
|
||
</SheetTitle>
|
||
<SheetDescription>
|
||
{sheetMode === 'edit'
|
||
? t('profile.profile_sheet.description_edit')
|
||
: t('profile.profile_sheet.description_add')
|
||
},{t('profile.profile_sheet.required_fields_note')}
|
||
</SheetDescription>
|
||
</SheetHeader>
|
||
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-2">
|
||
{/* 基本信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.basic_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
{/* 头像和表单字段 */}
|
||
<div className="flex gap-4 items-start">
|
||
{/* 头像上传区域 - 3:4 长方形 */}
|
||
<div className="flex-shrink-0">
|
||
<FormField
|
||
control={form.control}
|
||
name="avatar"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormControl>
|
||
<AvatarUpload
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
className="w-24 h-32"
|
||
placeholder={t('common.upload_avatar')}
|
||
accept="image/*"
|
||
maxSize={10 * 1024 * 1024} // 10MB
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 表单字段区域 */}
|
||
<div className="flex-1 space-y-4">
|
||
{/* 姓名和性别 */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="name"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.name') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.name')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="gender"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.gender') + ' *'}</FormLabel>
|
||
<Select
|
||
onValueChange={(value) => field.onChange(Number(value))}
|
||
value={field.value?.toString() || ''}
|
||
>
|
||
<FormControl>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.gender')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
<SelectItem value="1">{t('common.male')}</SelectItem>
|
||
<SelectItem value="2">{t('common.female')}</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 身份证号和证件号 */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="idNum"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.id_number') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.id_number')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="paperId"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.certificate_number') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.certificate_number')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 编制命令 - 占据一整行 */}
|
||
<FormField
|
||
control={form.control}
|
||
name="command"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.formation_command') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.formation_command')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
{/* 组织信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.organization_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<FormField
|
||
control={form.control}
|
||
name="organizationId"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.organization') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<DeptSelect
|
||
value={field.value}
|
||
onValueChange={field.onChange}
|
||
placeholder={t('profile.profile_sheet.placeholders.organization')}
|
||
className="w-full border border-[#EBEFF5]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
{/* 职务信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.duty_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="dutyName"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.duty_name') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<DutySelect
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
placeholder={t('profile.profile_sheet.placeholders.duty_name')}
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="dutyCode"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.duty_code')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.duty_code')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="dutyLevel"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.duty_level')}</FormLabel>
|
||
<FormControl>
|
||
<DutyLevelSelect
|
||
value={field.value}
|
||
onValueChange={field.onChange}
|
||
placeholder={t('profile.profile_sheet.duty_level_placeholder')}
|
||
className="w-full border border-[#EBEFF5]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
{/* 身份信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.identity_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="identity"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.identity') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.identity')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="level"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.rank') + ' *'}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.rank')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="levelDate"
|
||
render={({ field }) => (
|
||
<FormItem className="flex flex-col">
|
||
<FormLabel>{t('profile.profile_sheet.rank_time') + ' *'}</FormLabel>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<FormControl>
|
||
<Button
|
||
variant="outline"
|
||
className={cn(
|
||
"w-full pl-3 text-left font-normal border border-[#EBEFF5]",
|
||
!field.value && "text-muted-foreground"
|
||
)}
|
||
>
|
||
{field.value ? (
|
||
dayjs(field.value).format("YYYY-MM-DD")
|
||
) : (
|
||
<span>{t('common.select_date')}</span>
|
||
)}
|
||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||
</Button>
|
||
</FormControl>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={field.value}
|
||
onSelect={field.onChange}
|
||
disabled={(date) =>
|
||
date > new Date() || date < new Date("1900-01-01")
|
||
}
|
||
initialFocus
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
{/* 入职信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.hire_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="hireDate"
|
||
render={({ field }) => (
|
||
<FormItem className="flex flex-col">
|
||
<FormLabel>{t('profile.profile_sheet.hire_date') + ' *'}</FormLabel>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<FormControl>
|
||
<Button
|
||
variant="outline"
|
||
className={cn(
|
||
"w-full pl-3 text-left font-normal border border-[#EBEFF5]",
|
||
!field.value && "text-muted-foreground"
|
||
)}
|
||
>
|
||
{field.value ? (
|
||
dayjs(field.value).format("YYYY-MM-DD")
|
||
) : (
|
||
<span>{t('common.select_date')}</span>
|
||
)}
|
||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||
</Button>
|
||
</FormControl>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={field.value}
|
||
onSelect={field.onChange}
|
||
disabled={(date) =>
|
||
date > new Date() || date < new Date("1900-01-01")
|
||
}
|
||
initialFocus
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="relativeHireDate"
|
||
render={({ field }) => (
|
||
<FormItem className="flex flex-col">
|
||
<FormLabel>{t('profile.profile_sheet.relative_hire_time')}</FormLabel>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<FormControl>
|
||
<Button
|
||
variant="outline"
|
||
className={cn(
|
||
"w-full pl-3 text-left font-normal border border-[#EBEFF5]",
|
||
!field.value && "text-muted-foreground"
|
||
)}
|
||
>
|
||
{field.value ? (
|
||
dayjs(field.value).format("YYYY-MM-DD")
|
||
) : (
|
||
<span>{t('common.select_date')}</span>
|
||
)}
|
||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||
</Button>
|
||
</FormControl>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={field.value}
|
||
onSelect={field.onChange}
|
||
disabled={(date) =>
|
||
date > new Date() || date < new Date("1900-01-01")
|
||
}
|
||
initialFocus
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
{/* 详细信息 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">{t('profile.profile_sheet.detailed_info')}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
{/* 基本详细信息 */}
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.ethnicity"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.ethnicity')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.ethnicity')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="birthday"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.birth_date')}</FormLabel>
|
||
<FormControl>
|
||
<DateTimePicker
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
placeholder={t('profile.profile_sheet.placeholders.birth_date')}
|
||
showTime={false}
|
||
maxDate={new Date()}
|
||
minDate={new Date("1900-01-01")}
|
||
dateFormat="YYYY-MM-DD"
|
||
className='border border-[#EBEFF5]'
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.bloodType"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.blood_type')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full' >
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.blood_type')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
<SelectItem value="A">{t('profile.profile_sheet.blood_types.a')}</SelectItem>
|
||
<SelectItem value="B">{t('profile.profile_sheet.blood_types.b')}</SelectItem>
|
||
<SelectItem value="AB">{t('profile.profile_sheet.blood_types.ab')}</SelectItem>
|
||
<SelectItem value="O">{t('profile.profile_sheet.blood_types.o')}</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 政治面貌 */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.politicalStatus"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.political_status')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full'>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.political_status')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{politicalStatusOptions.map((status) => (
|
||
<SelectItem key={status} value={status}>
|
||
{status}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.partyPosition"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.party_position')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.party_position')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 籍贯 */}
|
||
<div className="space-y-2">
|
||
<FormLabel>{t('profile.profile_sheet.native_place')}</FormLabel>
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.native.province"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.province')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.native.city"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.city')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.native.county"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.county')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 学历信息 */}
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.education"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.education')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full'>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.education')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{educationOptions.map((education) => (
|
||
<SelectItem key={education} value={education}>
|
||
{education}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.educationForm"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.education_form')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full'>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.education_form')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{educationFormOptions.map((form) => (
|
||
<SelectItem key={form} value={form}>
|
||
{form}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.schoolMajor"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.school_major')}</FormLabel>
|
||
<FormControl className='w-full'>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.school_major')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 其他信息 */}
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.positionYear"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.position_year')}</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
placeholder={t('profile.profile_sheet.placeholders.position_year')}
|
||
{...field}
|
||
onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.foreignLanguage"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.foreign_language')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.foreign_language')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 培训经历 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<FormLabel>{t('profile.profile_sheet.train_experience')}</FormLabel>
|
||
<Button className='border border-[#5D6E89]' type="button" variant="outline" size="sm" onClick={addTrainExperience}>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
{t('profile.profile_sheet.add_train')}
|
||
</Button>
|
||
</div>
|
||
{form.watch('metadata.train')?.map((_, index) => (
|
||
<Card key={index} className="p-4">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="text-sm font-medium">{t('profile.profile_sheet.train_experience')} {index + 1}</h4>
|
||
{index > 0 && (
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => removeTrainExperience(index)}
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name={`metadata.train.${index}.type`}
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.train_type')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full'>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.train_type')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{trainTypeOptions.map((type) => (
|
||
<SelectItem key={type} value={type}>
|
||
{type}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name={`metadata.train.${index}.location`}
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.train_location')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.train_location')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
<FormField
|
||
control={form.control}
|
||
name={`metadata.train.${index}.major`}
|
||
render={({ field }) => (
|
||
<FormItem className="mt-4">
|
||
<FormLabel>{t('profile.profile_sheet.train_major')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.train_major')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
|
||
{/* 考核经历 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<FormLabel>{t('profile.profile_sheet.appraisal_experience')}</FormLabel>
|
||
<Button className='border border-[#5D6E89]' type="button" variant="outline" size="sm" onClick={addAppraisalExperience}>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
{t('profile.profile_sheet.add_appraisal')}
|
||
</Button>
|
||
</div>
|
||
{form.watch('metadata.appraisal')?.map((_, index) => (
|
||
<Card key={index} className="p-4">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="text-sm font-medium">{t('profile.profile_sheet.appraisal_experience')} {index + 1}</h4>
|
||
{index > 0 && (
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => removeAppraisalExperience(index)}
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name={`metadata.appraisal.${index}.level`}
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.appraisal_level')}</FormLabel>
|
||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||
<FormControl className='w-full'>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder={t('profile.profile_sheet.placeholders.appraisal_level')} />
|
||
</SelectTrigger>
|
||
</FormControl>
|
||
<SelectContent>
|
||
{appraisalLevelOptions.map((level) => (
|
||
<SelectItem key={level} value={level}>
|
||
{level}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name={`metadata.appraisal.${index}.job`}
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.appraisal_job')}</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder={t('profile.profile_sheet.placeholders.appraisal_job')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
|
||
{/* 奖励 - 独占一行 */}
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.reward"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.reward')}</FormLabel>
|
||
<FormControl>
|
||
<Textarea placeholder={t('profile.profile_sheet.placeholders.reward')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
{/* 处分 - 独占一行 */}
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.punish"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.punish')}</FormLabel>
|
||
<FormControl>
|
||
<Textarea placeholder={t('profile.profile_sheet.placeholders.punish')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="metadata.remark"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>{t('profile.profile_sheet.remark')}</FormLabel>
|
||
<FormControl>
|
||
<Textarea placeholder={t('profile.profile_sheet.placeholders.remark')} {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<SheetFooter className="gap-2">
|
||
<Button type="submit">
|
||
{t('common.confirm')}
|
||
</Button>
|
||
</SheetFooter>
|
||
</form>
|
||
</Form>
|
||
</SheetContent>
|
||
</Sheet>
|
||
);
|
||
}
|