init: AI HR Compliance Assistant
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { DollarSign, Check } from 'lucide-react'
|
||||
import api from '../../lib/api'
|
||||
import Card from '../../components/ui/Card'
|
||||
import Button from '../../components/ui/Button'
|
||||
import EmptyState from '../../components/ui/EmptyState'
|
||||
|
||||
const portalApi = api.create({ baseURL: '/api/v1/portal' })
|
||||
portalApi.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('portalToken')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
|
||||
export default function Payslip() {
|
||||
const queryClient = useQueryClient()
|
||||
const [month, setMonth] = useState(new Date().toISOString().slice(0, 7))
|
||||
|
||||
const { data, isLoading } = useQuery<any>({
|
||||
queryKey: ['payslip', month],
|
||||
queryFn: async () => {
|
||||
const res = await portalApi.get('/payslip', { params: { month } }) as any
|
||||
return res.data?.data ?? null
|
||||
},
|
||||
})
|
||||
|
||||
const confirmMutation = useMutation({
|
||||
mutationFn: (id: string) => portalApi.post(`/payslip/${id}/confirm`),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['payslip'] }),
|
||||
})
|
||||
|
||||
const employee = JSON.parse(localStorage.getItem('portalEmployee') || '{}')
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 px-4 py-6">
|
||||
<div className="max-w-md mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<DollarSign className="w-6 h-6 text-primary" />
|
||||
<h1 className="text-lg font-semibold">我的工资条</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-500">{employee.name}</span>
|
||||
<Link to="/portal/contract" className="text-sm text-primary">我的合同</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<input
|
||||
type="month"
|
||||
value={month}
|
||||
onChange={(e) => setMonth(e.target.value)}
|
||||
className="px-3 py-2 rounded-md border border-gray-300 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8 text-gray-400">加载中...</div>
|
||||
) : !data ? (
|
||||
<EmptyState title="暂无工资条" description={`该月份(${month})暂无工资记录`} />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">基本工资</span>
|
||||
<span className="font-medium">¥{Number(data.baseSalary).toLocaleString()}</span>
|
||||
</div>
|
||||
{data.overtimePay > 0 && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">加班费</span>
|
||||
<span className="font-medium">¥{Number(data.overtimePay).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data.allowance > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">津贴</span>
|
||||
<span className="font-medium">¥{Number(data.allowance).toLocaleString()}</span>
|
||||
</div>
|
||||
)}
|
||||
{data.deduction > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">扣款</span>
|
||||
<span className="font-medium text-danger">-¥{Number(data.deduction).toLocaleString()}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t pt-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">应发合计</span>
|
||||
<span className="text-xl font-bold text-primary">¥{Number(data.totalPay).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.confirmedAt ? (
|
||||
<div className="flex items-center gap-2 text-sm text-safe">
|
||||
<Check className="w-4 h-4" /> 已确认({new Date(data.confirmedAt).toLocaleString()})
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => confirmMutation.mutate(data.id)}
|
||||
disabled={confirmMutation.isPending}
|
||||
>
|
||||
{confirmMutation.isPending ? '确认中...' : '确认已阅'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user