|
|
@@ -0,0 +1,650 @@
|
|
|
+import React, { useState } from 'react';
|
|
|
+import { useQuery, useMutation } from '@tanstack/react-query';
|
|
|
+import { Plus, Edit, Trash2, Search, Calculator } from 'lucide-react';
|
|
|
+import { format } from 'date-fns';
|
|
|
+import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
|
+import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@d8d/shared-ui-components/components/ui/card';
|
|
|
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
|
|
|
+import { Skeleton } from '@d8d/shared-ui-components/components/ui/skeleton';
|
|
|
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@d8d/shared-ui-components/components/ui/dialog';
|
|
|
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@d8d/shared-ui-components/components/ui/form';
|
|
|
+import { useForm } from 'react-hook-form';
|
|
|
+import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
+import { toast } from 'sonner';
|
|
|
+import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
|
+import { AreaSelect } from '@d8d/area-management-ui/components';
|
|
|
+import { salaryClientManager } from '../api/salaryClient';
|
|
|
+import { CreateSalarySchema, UpdateSalarySchema } from '@d8d/allin-salary-module/schemas';
|
|
|
+import type { CreateSalaryDto, UpdateSalaryDto, SalaryLevel, QuerySalaryDto } from '@d8d/allin-salary-module/schemas';
|
|
|
+
|
|
|
+interface SalarySearchParams {
|
|
|
+ page: number;
|
|
|
+ limit: number;
|
|
|
+ provinceId?: number;
|
|
|
+ cityId?: number;
|
|
|
+}
|
|
|
+
|
|
|
+const SalaryManagement: React.FC = () => {
|
|
|
+ const [searchParams, setSearchParams] = useState<SalarySearchParams>({ page: 1, limit: 10 });
|
|
|
+ const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
+ const [isCreateForm, setIsCreateForm] = useState(true);
|
|
|
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
+ const [salaryToDelete, setSalaryToDelete] = useState<number | null>(null);
|
|
|
+ const [areaValue, setAreaValue] = useState<{ provinceId?: number; cityId?: number; districtId?: number }>({});
|
|
|
+
|
|
|
+ // 表单实例
|
|
|
+ const createForm = useForm<CreateSalaryDto>({
|
|
|
+ resolver: zodResolver(CreateSalarySchema),
|
|
|
+ defaultValues: {
|
|
|
+ basicSalary: 0,
|
|
|
+ allowance: 0,
|
|
|
+ insurance: 0,
|
|
|
+ housingFund: 0
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const updateForm = useForm<UpdateSalaryDto>({
|
|
|
+ resolver: zodResolver(UpdateSalarySchema),
|
|
|
+ defaultValues: {}
|
|
|
+ });
|
|
|
+
|
|
|
+ // 计算总薪资
|
|
|
+ const calculateTotalSalary = (basicSalary: number, allowance: number, insurance: number, housingFund: number) => {
|
|
|
+ return basicSalary + (allowance || 0) - (insurance || 0) - (housingFund || 0);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 数据查询
|
|
|
+ const { data, isLoading, refetch } = useQuery({
|
|
|
+ queryKey: ['salaries', searchParams],
|
|
|
+ queryFn: async () => {
|
|
|
+ const query: QuerySalaryDto = {
|
|
|
+ skip: (searchParams.page - 1) * searchParams.limit,
|
|
|
+ take: searchParams.limit
|
|
|
+ };
|
|
|
+
|
|
|
+ if (searchParams.provinceId) query.provinceId = searchParams.provinceId;
|
|
|
+ if (searchParams.cityId) query.cityId = searchParams.cityId;
|
|
|
+
|
|
|
+ const res = await salaryClientManager.get().$get({ query });
|
|
|
+ if (res.status !== 200) throw new Error('获取薪资列表失败');
|
|
|
+ return await res.json();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建薪资
|
|
|
+ const createMutation = useMutation({
|
|
|
+ mutationFn: async (data: CreateSalaryDto) => {
|
|
|
+ const res = await salaryClientManager.get().create.$post({ json: data });
|
|
|
+ if (res.status !== 200) throw new Error('创建薪资失败');
|
|
|
+ return await res.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ toast.success('薪资创建成功');
|
|
|
+ setIsModalOpen(false);
|
|
|
+ createForm.reset();
|
|
|
+ setAreaValue({});
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ toast.error(error instanceof Error ? error.message : '创建薪资失败');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新薪资
|
|
|
+ const updateMutation = useMutation({
|
|
|
+ mutationFn: async (data: UpdateSalaryDto) => {
|
|
|
+ const res = await salaryClientManager.get().update[':id'].$put({
|
|
|
+ param: { id: data.id! },
|
|
|
+ json: data
|
|
|
+ });
|
|
|
+ if (res.status !== 200) throw new Error('更新薪资失败');
|
|
|
+ return await res.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ toast.success('薪资更新成功');
|
|
|
+ setIsModalOpen(false);
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ toast.error(error instanceof Error ? error.message : '更新薪资失败');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 删除薪资
|
|
|
+ const deleteMutation = useMutation({
|
|
|
+ mutationFn: async (id: number) => {
|
|
|
+ const res = await salaryClientManager.get().delete[':id'].$delete({ param: { id } });
|
|
|
+ if (res.status !== 200) throw new Error('删除薪资失败');
|
|
|
+ return await res.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ toast.success('薪资删除成功');
|
|
|
+ setDeleteDialogOpen(false);
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ toast.error(error instanceof Error ? error.message : '删除薪资失败');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理搜索
|
|
|
+ const handleSearch = () => {
|
|
|
+ setSearchParams(prev => ({
|
|
|
+ ...prev,
|
|
|
+ page: 1,
|
|
|
+ provinceId: areaValue.provinceId,
|
|
|
+ cityId: areaValue.cityId
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理分页变化
|
|
|
+ const handlePaginationChange = (page: number, limit: number) => {
|
|
|
+ setSearchParams(prev => ({ ...prev, page, limit }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 显示创建模态框
|
|
|
+ const showCreateModal = () => {
|
|
|
+ setIsCreateForm(true);
|
|
|
+ createForm.reset({
|
|
|
+ basicSalary: 0,
|
|
|
+ allowance: 0,
|
|
|
+ insurance: 0,
|
|
|
+ housingFund: 0
|
|
|
+ });
|
|
|
+ setAreaValue({});
|
|
|
+ setIsModalOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 显示编辑模态框
|
|
|
+ const showEditModal = (salary: SalaryLevel) => {
|
|
|
+ setIsCreateForm(false);
|
|
|
+ updateForm.reset({
|
|
|
+ id: salary.id,
|
|
|
+ provinceId: salary.provinceId,
|
|
|
+ cityId: salary.cityId,
|
|
|
+ districtId: salary.districtId || undefined,
|
|
|
+ basicSalary: salary.basicSalary,
|
|
|
+ allowance: salary.allowance,
|
|
|
+ insurance: salary.insurance,
|
|
|
+ housingFund: salary.housingFund
|
|
|
+ });
|
|
|
+ setAreaValue({
|
|
|
+ provinceId: salary.provinceId,
|
|
|
+ cityId: salary.cityId,
|
|
|
+ districtId: salary.districtId || undefined
|
|
|
+ });
|
|
|
+ setIsModalOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 显示删除对话框
|
|
|
+ const showDeleteDialog = (id: number) => {
|
|
|
+ setSalaryToDelete(id);
|
|
|
+ setDeleteDialogOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理创建表单提交
|
|
|
+ const handleCreateSubmit = (data: CreateSalaryDto) => {
|
|
|
+ const submitData = {
|
|
|
+ ...data,
|
|
|
+ provinceId: areaValue.provinceId!,
|
|
|
+ cityId: areaValue.cityId!,
|
|
|
+ districtId: areaValue.districtId
|
|
|
+ };
|
|
|
+ createMutation.mutate(submitData);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理更新表单提交
|
|
|
+ const handleUpdateSubmit = (data: UpdateSalaryDto) => {
|
|
|
+ updateMutation.mutate(data);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理区域选择变化
|
|
|
+ const handleAreaChange = (value: { provinceId?: number; cityId?: number; districtId?: number }) => {
|
|
|
+ setAreaValue(value);
|
|
|
+ if (isCreateForm) {
|
|
|
+ createForm.setValue('provinceId', value.provinceId!);
|
|
|
+ createForm.setValue('cityId', value.cityId!);
|
|
|
+ createForm.setValue('districtId', value.districtId);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理数值变化,实时计算总薪资
|
|
|
+ const handleValueChange = (field: keyof CreateSalaryDto, value: number) => {
|
|
|
+ if (isCreateForm) {
|
|
|
+ const formValues = createForm.getValues();
|
|
|
+ const total = calculateTotalSalary(
|
|
|
+ field === 'basicSalary' ? value : formValues.basicSalary,
|
|
|
+ field === 'allowance' ? value : (formValues.allowance || 0),
|
|
|
+ field === 'insurance' ? value : (formValues.insurance || 0),
|
|
|
+ field === 'housingFund' ? value : (formValues.housingFund || 0)
|
|
|
+ );
|
|
|
+ // 可以在这里显示总薪资,但不需要保存到表单
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Card className="w-full">
|
|
|
+ <CardHeader>
|
|
|
+ <CardTitle className="flex items-center gap-2">
|
|
|
+ <Calculator className="h-5 w-5" />
|
|
|
+ 薪资水平管理
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ 管理各地区薪资水平,支持基本工资、津贴、保险、公积金等计算
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent>
|
|
|
+ {/* 搜索区域 */}
|
|
|
+ <div className="flex flex-wrap items-center gap-4 mb-6">
|
|
|
+ <div className="flex-1 min-w-[300px]">
|
|
|
+ <AreaSelect
|
|
|
+ value={areaValue}
|
|
|
+ onChange={handleAreaChange}
|
|
|
+ disabled={false}
|
|
|
+ required={false}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <Button onClick={handleSearch}>
|
|
|
+ <Search className="h-4 w-4 mr-2" />
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ <Button onClick={showCreateModal}>
|
|
|
+ <Plus className="h-4 w-4 mr-2" />
|
|
|
+ 添加薪资
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 数据表格 */}
|
|
|
+ {isLoading ? (
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Skeleton className="h-8 w-full" />
|
|
|
+ <Skeleton className="h-8 w-full" />
|
|
|
+ <Skeleton className="h-8 w-full" />
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <Table>
|
|
|
+ <TableHeader>
|
|
|
+ <TableRow>
|
|
|
+ <TableHead>ID</TableHead>
|
|
|
+ <TableHead>省份</TableHead>
|
|
|
+ <TableHead>城市</TableHead>
|
|
|
+ <TableHead>区县</TableHead>
|
|
|
+ <TableHead>基本工资</TableHead>
|
|
|
+ <TableHead>津贴补贴</TableHead>
|
|
|
+ <TableHead>保险费用</TableHead>
|
|
|
+ <TableHead>公积金</TableHead>
|
|
|
+ <TableHead>总薪资</TableHead>
|
|
|
+ <TableHead>更新时间</TableHead>
|
|
|
+ <TableHead>操作</TableHead>
|
|
|
+ </TableRow>
|
|
|
+ </TableHeader>
|
|
|
+ <TableBody>
|
|
|
+ {data?.data?.map((salary: SalaryLevel) => (
|
|
|
+ <TableRow key={salary.id}>
|
|
|
+ <TableCell>{salary.id}</TableCell>
|
|
|
+ <TableCell>{salary.province?.name || salary.provinceId}</TableCell>
|
|
|
+ <TableCell>{salary.city?.name || salary.cityId}</TableCell>
|
|
|
+ <TableCell>{salary.district?.name || salary.districtId || '-'}</TableCell>
|
|
|
+ <TableCell>¥{salary.basicSalary.toFixed(2)}</TableCell>
|
|
|
+ <TableCell>¥{(salary.allowance || 0).toFixed(2)}</TableCell>
|
|
|
+ <TableCell>¥{(salary.insurance || 0).toFixed(2)}</TableCell>
|
|
|
+ <TableCell>¥{(salary.housingFund || 0).toFixed(2)}</TableCell>
|
|
|
+ <TableCell className="font-semibold">¥{salary.totalSalary.toFixed(2)}</TableCell>
|
|
|
+ <TableCell>{format(new Date(salary.updateTime), 'yyyy-MM-dd HH:mm')}</TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <div className="flex gap-2">
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => showEditModal(salary)}
|
|
|
+ >
|
|
|
+ <Edit className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => showDeleteDialog(salary.id)}
|
|
|
+ >
|
|
|
+ <Trash2 className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ </TableRow>
|
|
|
+ ))}
|
|
|
+ </TableBody>
|
|
|
+ </Table>
|
|
|
+
|
|
|
+ {/* 分页 */}
|
|
|
+ {data?.total && (
|
|
|
+ <DataTablePagination
|
|
|
+ currentPage={searchParams.page}
|
|
|
+ pageSize={searchParams.limit}
|
|
|
+ total={data.total}
|
|
|
+ onPageChange={(page) => handlePaginationChange(page, searchParams.limit)}
|
|
|
+ onPageSizeChange={(limit) => handlePaginationChange(1, limit)}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 创建/编辑模态框 */}
|
|
|
+ <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
|
|
+ <DialogContent className="max-w-2xl">
|
|
|
+ <DialogHeader>
|
|
|
+ <DialogTitle>{isCreateForm ? '添加薪资' : '编辑薪资'}</DialogTitle>
|
|
|
+ <DialogDescription>
|
|
|
+ {isCreateForm ? '填写薪资信息,支持实时计算总薪资' : '修改薪资信息'}
|
|
|
+ </DialogDescription>
|
|
|
+ </DialogHeader>
|
|
|
+
|
|
|
+ {isCreateForm ? (
|
|
|
+ <Form {...createForm}>
|
|
|
+ <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
|
|
|
+ {/* 区域选择器 */}
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="provinceId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>区域选择</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <AreaSelect
|
|
|
+ value={areaValue}
|
|
|
+ onChange={handleAreaChange}
|
|
|
+ required={true}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 基本工资 */}
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="basicSalary"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>基本工资</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ onChange={(e) => {
|
|
|
+ const value = parseFloat(e.target.value) || 0;
|
|
|
+ field.onChange(value);
|
|
|
+ handleValueChange('basicSalary', value);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 津贴补贴 */}
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="allowance"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>津贴补贴</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ onChange={(e) => {
|
|
|
+ const value = parseFloat(e.target.value) || 0;
|
|
|
+ field.onChange(value);
|
|
|
+ handleValueChange('allowance', value);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 保险费用 */}
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="insurance"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>保险费用</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ onChange={(e) => {
|
|
|
+ const value = parseFloat(e.target.value) || 0;
|
|
|
+ field.onChange(value);
|
|
|
+ handleValueChange('insurance', value);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 住房公积金 */}
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="housingFund"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>住房公积金</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ onChange={(e) => {
|
|
|
+ const value = parseFloat(e.target.value) || 0;
|
|
|
+ field.onChange(value);
|
|
|
+ handleValueChange('housingFund', value);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 总薪资计算显示 */}
|
|
|
+ <div className="p-4 bg-muted rounded-lg">
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
+ <span className="font-medium">计算总薪资:</span>
|
|
|
+ <span className="text-2xl font-bold text-primary">
|
|
|
+ ¥{calculateTotalSalary(
|
|
|
+ createForm.watch('basicSalary'),
|
|
|
+ createForm.watch('allowance') || 0,
|
|
|
+ createForm.watch('insurance') || 0,
|
|
|
+ createForm.watch('housingFund') || 0
|
|
|
+ ).toFixed(2)}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="text-sm text-muted-foreground mt-2">
|
|
|
+ 计算公式:基本工资 + 津贴 - 保险 - 公积金
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <DialogFooter>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="outline"
|
|
|
+ onClick={() => setIsModalOpen(false)}
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button type="submit" disabled={createMutation.isPending}>
|
|
|
+ {createMutation.isPending ? '创建中...' : '创建薪资'}
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ ) : (
|
|
|
+ <Form {...updateForm}>
|
|
|
+ <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
|
|
|
+ {/* 显示当前区域信息 */}
|
|
|
+ <div className="grid grid-cols-3 gap-4">
|
|
|
+ <div>
|
|
|
+ <FormLabel>省份</FormLabel>
|
|
|
+ <div className="p-2 border rounded-md bg-muted">
|
|
|
+ {updateForm.watch('provinceId')}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <FormLabel>城市</FormLabel>
|
|
|
+ <div className="p-2 border rounded-md bg-muted">
|
|
|
+ {updateForm.watch('cityId')}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <FormLabel>区县</FormLabel>
|
|
|
+ <div className="p-2 border rounded-md bg-muted">
|
|
|
+ {updateForm.watch('districtId') || '-'}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 基本工资 */}
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="basicSalary"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>基本工资</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 津贴补贴 */}
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="allowance"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>津贴补贴</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 保险费用 */}
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="insurance"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>保险费用</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 住房公积金 */}
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="housingFund"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>住房公积金</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ step="0.01"
|
|
|
+ min="0"
|
|
|
+ {...field}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <DialogFooter>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="outline"
|
|
|
+ onClick={() => setIsModalOpen(false)}
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button type="submit" disabled={updateMutation.isPending}>
|
|
|
+ {updateMutation.isPending ? '更新中...' : '更新薪资'}
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ )}
|
|
|
+ </DialogContent>
|
|
|
+ </Dialog>
|
|
|
+
|
|
|
+ {/* 删除确认对话框 */}
|
|
|
+ <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
|
+ <DialogContent>
|
|
|
+ <DialogHeader>
|
|
|
+ <DialogTitle>确认删除</DialogTitle>
|
|
|
+ <DialogDescription>
|
|
|
+ 确定要删除这条薪资信息吗?此操作不可撤销。
|
|
|
+ </DialogDescription>
|
|
|
+ </DialogHeader>
|
|
|
+ <DialogFooter>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ onClick={() => setDeleteDialogOpen(false)}
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="destructive"
|
|
|
+ onClick={() => salaryToDelete && deleteMutation.mutate(salaryToDelete)}
|
|
|
+ disabled={deleteMutation.isPending}
|
|
|
+ >
|
|
|
+ {deleteMutation.isPending ? '删除中...' : '确认删除'}
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </DialogContent>
|
|
|
+ </Dialog>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default SalaryManagement;
|