feat: 全部列表页统一分页 + WorkProcess集成现有服务
- WorkProcess/Termination/Evidence/Contracts/Policies/Templates 添加 Pagination 组件 - 后端 getDrafts/getPolicies/enterprise-template 路由添加分页支持(可选参数,向后兼容) - work-process.service CONFIRM case 修正:移除不存在的 probationEndDate 字段 - 新增 migration_add_three_tables.sql 增量迁移文件
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
-- 增量迁移:添加 WorkProcess、EnterpriseTemplate、AttendancePublish 三张表
|
||||
-- 不影响现有数据,仅 CREATE TABLE IF NOT EXISTS
|
||||
-- 执行方式: PGPASSWORD=turbohr2026 psql -U turbohr -d turbohr -f migration_add_three_tables.sql
|
||||
|
||||
-- 1. WorkProcess 用工办理工作流
|
||||
CREATE TABLE IF NOT EXISTS "WorkProcess" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"employeeId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'DRAFT',
|
||||
"formData" JSONB NOT NULL,
|
||||
"documents" JSONB,
|
||||
"approverId" TEXT,
|
||||
"approvedAt" TIMESTAMP(3),
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "WorkProcess_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- 外键:orgId -> Organization
|
||||
ALTER TABLE "WorkProcess"
|
||||
ADD CONSTRAINT "WorkProcess_orgId_fkey"
|
||||
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
|
||||
|
||||
-- 外键:employeeId -> Employee (ON DELETE SET NULL)
|
||||
ALTER TABLE "WorkProcess"
|
||||
ADD CONSTRAINT "WorkProcess_employeeId_fkey"
|
||||
FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE SET NULL;
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_status_idx" ON "WorkProcess"("orgId", "status");
|
||||
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_type_idx" ON "WorkProcess"("orgId", "type");
|
||||
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_createdBy_idx" ON "WorkProcess"("orgId", "createdBy");
|
||||
|
||||
-- 2. EnterpriseTemplate 企业自建文本模板
|
||||
CREATE TABLE IF NOT EXISTS "EnterpriseTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"content" TEXT NOT NULL,
|
||||
"variables" TEXT[] NOT NULL DEFAULT '{}',
|
||||
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EnterpriseTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "EnterpriseTemplate"
|
||||
ADD CONSTRAINT "EnterpriseTemplate_orgId_fkey"
|
||||
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "EnterpriseTemplate_orgId_category_idx" ON "EnterpriseTemplate"("orgId", "category");
|
||||
|
||||
-- 3. AttendancePublish 考勤发布记录
|
||||
CREATE TABLE IF NOT EXISTS "AttendancePublish" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"month" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PUBLISHED',
|
||||
"publishDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AttendancePublish_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "AttendancePublish"
|
||||
ADD CONSTRAINT "AttendancePublish_orgId_fkey"
|
||||
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
|
||||
|
||||
-- 唯一约束:同一组织同月只能有一条发布记录
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "AttendancePublish_orgId_month_key" ON "AttendancePublish"("orgId", "month");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "AttendancePublish_orgId_month_idx" ON "AttendancePublish"("orgId", "month");
|
||||
@@ -15,14 +15,21 @@ function extractVariables(content: string): string[] {
|
||||
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { category, search } = req.query
|
||||
const page = parseInt(req.query.page as string) || 1
|
||||
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
|
||||
const where: any = { orgId: req.user!.orgId, status: 'ACTIVE' }
|
||||
if (category) where.category = category
|
||||
if (search) where.name = { contains: String(search) }
|
||||
const items = await (prisma as any).enterpriseTemplate.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
res.json({ success: true, data: items })
|
||||
const [items, total] = await Promise.all([
|
||||
(prisma as any).enterpriseTemplate.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
(prisma as any).enterpriseTemplate.count({ where }),
|
||||
])
|
||||
res.json({ success: true, data: { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) } })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ const router = Router()
|
||||
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const status = req.query.status as string | undefined
|
||||
const policies = await getPolicies(req.user!.orgId, status)
|
||||
res.json({ success: true, data: policies })
|
||||
const page = parseInt(req.query.page as string) || 1
|
||||
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
|
||||
const result = await getPolicies(req.user!.orgId, status, page, pageSize)
|
||||
res.json({ success: true, data: result })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,9 @@ router.get('/drafts', authMiddleware, async (req: AuthRequest, res, next) => {
|
||||
const status = req.query.status as string | undefined
|
||||
const search = req.query.search as string | undefined
|
||||
const department = req.query.department as string | undefined
|
||||
const result = await getDrafts(req.user!.orgId, status, search, department)
|
||||
const page = parseInt(req.query.page as string) || 1
|
||||
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
|
||||
const result = await getDrafts(req.user!.orgId, status, search, department, page, pageSize)
|
||||
res.json({ success: true, data: result })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
|
||||
@@ -73,24 +73,31 @@ export async function createPolicy(orgId: string, userId: string, data: { title:
|
||||
/**
|
||||
* 获取制度列表(含阅读签收统计)
|
||||
*/
|
||||
export async function getPolicies(orgId: string, status?: string) {
|
||||
export async function getPolicies(orgId: string, status?: string, page?: number, pageSize?: number) {
|
||||
const where: any = { orgId }
|
||||
if (status) where.status = status
|
||||
const [policies, totalEmployees] = await Promise.all([
|
||||
const hasPagination = page && pageSize
|
||||
const [policies, totalEmployees, total] = await Promise.all([
|
||||
prisma.policyDocument.findMany({
|
||||
where,
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
include: {
|
||||
_count: { select: { readRecords: true } },
|
||||
},
|
||||
...(hasPagination ? { skip: (page! - 1) * pageSize!, take: pageSize! } : {}),
|
||||
}),
|
||||
prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }),
|
||||
hasPagination ? prisma.policyDocument.count({ where }) : Promise.resolve(0),
|
||||
])
|
||||
return policies.map(p => ({
|
||||
const items = policies.map(p => ({
|
||||
...p,
|
||||
readCount: p._count?.readRecords || 0,
|
||||
totalEmployees,
|
||||
}))
|
||||
if (hasPagination) {
|
||||
return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize!) }
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -738,7 +738,7 @@ export async function cancelTermination(orgId: string, recordId: string, userId:
|
||||
}
|
||||
|
||||
/** 获取草稿列表 */
|
||||
export async function getDrafts(orgId: string, status?: string, search?: string, department?: string) {
|
||||
export async function getDrafts(orgId: string, status?: string, search?: string, department?: string, page?: number, pageSize?: number) {
|
||||
const where: any = { orgId }
|
||||
if (status) {
|
||||
where.status = status
|
||||
@@ -758,13 +758,18 @@ export async function getDrafts(orgId: string, status?: string, search?: string,
|
||||
}
|
||||
}
|
||||
|
||||
const records = await prisma.terminationRecord.findMany({
|
||||
where,
|
||||
include: { employee: true },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
})
|
||||
const hasPagination = page && pageSize
|
||||
const [records, total] = await Promise.all([
|
||||
prisma.terminationRecord.findMany({
|
||||
where,
|
||||
include: { employee: true },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
...(hasPagination ? { skip: (page! - 1) * pageSize!, take: pageSize! } : {}),
|
||||
}),
|
||||
hasPagination ? prisma.terminationRecord.count({ where }) : Promise.resolve(0),
|
||||
])
|
||||
|
||||
return records.map((r) => ({
|
||||
const items = records.map((r) => ({
|
||||
id: r.id,
|
||||
employeeId: r.employeeId,
|
||||
employeeName: r.employee.name,
|
||||
@@ -780,6 +785,11 @@ export async function getDrafts(orgId: string, status?: string, search?: string,
|
||||
createdAt: r.createdAt.toISOString().slice(0, 10),
|
||||
updatedAt: r.updatedAt.toISOString().slice(0, 10),
|
||||
}))
|
||||
|
||||
if (hasPagination) {
|
||||
return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize!) }
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/** 获取单条记录详情(含所有流程字段) */
|
||||
|
||||
@@ -69,17 +69,10 @@ export async function executeWorkProcess(processId: string, type: string, formDa
|
||||
case 'CONFIRM': {
|
||||
const { employeeId, confirmDate, regularSalary } = formData
|
||||
if (employeeId) {
|
||||
const updateData: any = {}
|
||||
if (regularSalary) {
|
||||
updateData.monthlySalary = encrypt(String(regularSalary))
|
||||
}
|
||||
if (confirmDate) {
|
||||
updateData.probationEndDate = new Date(confirmDate)
|
||||
}
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await prisma.employee.update({
|
||||
where: { id: employeeId },
|
||||
data: updateData,
|
||||
data: { monthlySalary: encrypt(String(regularSalary)) },
|
||||
})
|
||||
await runRiskDetection(orgId)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Button from '../components/ui/Button'
|
||||
import { Input, Label, Select } from '../components/ui/Input'
|
||||
import Modal from '../components/ui/Modal'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
interface EmployeeItem {
|
||||
id: string
|
||||
@@ -37,13 +38,14 @@ export default function Contracts() {
|
||||
const [filterDepartment, setFilterDepartment] = useState('')
|
||||
const [filterContractStatus, setFilterContractStatus] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [showAddModal, setShowAddModal] = useState(false)
|
||||
const [selectedEmpId, setSelectedEmpId] = useState<string | null>(null)
|
||||
|
||||
const { data, isLoading } = useQuery<EmployeeListResponse>({
|
||||
queryKey: ['employees', search, filterDepartment, filterContractStatus, page],
|
||||
queryKey: ['employees', search, filterDepartment, filterContractStatus, page, pageSize],
|
||||
queryFn: async () => {
|
||||
const params: any = { search, page, pageSize: 20 }
|
||||
const params: any = { search, page, pageSize }
|
||||
if (filterDepartment) params.department = filterDepartment
|
||||
if (filterContractStatus) params.contractStatus = filterContractStatus
|
||||
const res = await api.get('/roster', { params }) as any
|
||||
@@ -181,23 +183,13 @@ export default function Contracts() {
|
||||
</div>
|
||||
|
||||
{/* 分页 */}
|
||||
{data.totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-2 mt-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={page === 1}
|
||||
onClick={() => setPage(p => p - 1)}
|
||||
>上一页</Button>
|
||||
<span className="text-xs text-gray-500">{page} / {data.totalPages}</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={page === data.totalPages}
|
||||
onClick={() => setPage(p => p + 1)}
|
||||
>下一页</Button>
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={data.total}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -4,21 +4,27 @@ import { ShieldCheck, FileText, AlertCircle, CheckCircle, XCircle } from 'lucide
|
||||
import api from '../lib/api'
|
||||
import Card from '../components/ui/Card'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
/**
|
||||
* 证据链管理页面
|
||||
*/
|
||||
export default function Evidence() {
|
||||
const [refType, setRefType] = useState<string>('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
|
||||
const { data: list, isLoading } = useQuery<any>({
|
||||
queryKey: ['evidence', refType],
|
||||
const { data: listData, isLoading } = useQuery<any>({
|
||||
queryKey: ['evidence', refType, page, pageSize],
|
||||
queryFn: async () => {
|
||||
const params = refType ? `?category=${refType}` : '?category=ALL'
|
||||
const res = await api.get(`/evidence${params}`) as any
|
||||
return res.data?.records || []
|
||||
const params: any = { page, pageSize }
|
||||
params.category = refType || 'ALL'
|
||||
const res = await api.get('/evidence', { params }) as any
|
||||
return res.data
|
||||
},
|
||||
})
|
||||
const list = listData?.records || []
|
||||
const total = listData?.total || 0
|
||||
|
||||
const { data: verifyResult, refetch: verifyAll } = useQuery<any>({
|
||||
queryKey: ['evidence-verify-all'],
|
||||
@@ -68,7 +74,7 @@ export default function Evidence() {
|
||||
{['', 'ONBOARD', 'CONTRACT_SIGN', 'PAYSLIP_CONFIRM', 'DISCIPLINARY', 'ATTENDANCE', 'TERMINATION'].map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
onClick={() => setRefType(t)}
|
||||
onClick={() => { setRefType(t); setPage(1) }}
|
||||
className={`px-3 py-1 text-xs rounded-lg ${refType === t ? 'bg-primary text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'}`}
|
||||
>
|
||||
{t === '' ? '全部' : t === 'DISCIPLINARY' ? '违纪' : t === 'TERMINATION' ? '解聘' : t === 'CONTRACT_SIGN' ? '合同' : t === 'PAYSLIP_CONFIRM' ? '薪酬' : t === 'ONBOARD' ? '入职' : '考勤'}
|
||||
@@ -112,6 +118,13 @@ export default function Evidence() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import api from '../lib/api'
|
||||
import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
const STEP_LABELS: Record<string, string> = {
|
||||
DRAFTING: '起草',
|
||||
@@ -23,14 +24,18 @@ export default function Policies() {
|
||||
const queryClient = useQueryClient()
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<any>(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
|
||||
const { data: list, isLoading } = useQuery<any>({
|
||||
queryKey: ['policies'],
|
||||
const { data: listData, isLoading } = useQuery<any>({
|
||||
queryKey: ['policies', page, pageSize],
|
||||
queryFn: async () => {
|
||||
const res = await api.get('/policies') as any
|
||||
const res = await api.get('/policies', { params: { page, pageSize } }) as any
|
||||
return res.data
|
||||
},
|
||||
})
|
||||
const list = listData?.items || []
|
||||
const total = listData?.total || 0
|
||||
|
||||
const advanceMutation = useMutation({
|
||||
mutationFn: ({ id, step, note }: { id: string; step: number; note?: string }) => api.post(`/policies/${id}/advance-step`, { step, note }),
|
||||
@@ -114,6 +119,13 @@ export default function Policies() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
|
||||
/>
|
||||
|
||||
{/* 详情弹窗 */}
|
||||
{selectedPolicy && (
|
||||
|
||||
@@ -9,6 +9,7 @@ import Button from '../components/ui/Button'
|
||||
import { Input, Label, Select } from '../components/ui/Input'
|
||||
import Modal from '../components/ui/Modal'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
CONTRACT: '合同',
|
||||
@@ -303,6 +304,8 @@ function SystemTemplates() {
|
||||
function EnterpriseTemplates() {
|
||||
const queryClient = useQueryClient()
|
||||
const [category, setCategory] = useState<string>('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
const [editItem, setEditItem] = useState<any>(null)
|
||||
const [form, setForm] = useState({ name: '', category: 'CONTRACT', description: '', content: '' })
|
||||
@@ -310,14 +313,17 @@ function EnterpriseTemplates() {
|
||||
const [rendered, setRendered] = useState('')
|
||||
const [variables, setVariables] = useState<Record<string, string>>({})
|
||||
|
||||
const { data: list, isLoading } = useQuery<any>({
|
||||
queryKey: ['enterprise-templates', category],
|
||||
const { data: listData, isLoading } = useQuery<any>({
|
||||
queryKey: ['enterprise-templates', category, page, pageSize],
|
||||
queryFn: async () => {
|
||||
const params = category ? `?category=${category}` : ''
|
||||
const res = await api.get(`/enterprise-templates${params}`) as any
|
||||
const params: any = { page, pageSize }
|
||||
if (category) params.category = category
|
||||
const res = await api.get('/enterprise-templates', { params }) as any
|
||||
return res.data
|
||||
},
|
||||
})
|
||||
const list = listData?.items || []
|
||||
const total = listData?.total || 0
|
||||
|
||||
const { data: detail } = useQuery<any>({
|
||||
queryKey: ['enterprise-template-detail', selected?.id],
|
||||
@@ -459,6 +465,13 @@ function EnterpriseTemplates() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
|
||||
/>
|
||||
|
||||
{/* 编辑弹窗 */}
|
||||
<Modal open={showEdit} onClose={() => setShowEdit(false)} title={editItem ? '编辑模板' : '新建模板'} size="lg">
|
||||
|
||||
@@ -9,6 +9,7 @@ import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
import { Input, Label, Select } from '../components/ui/Input'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
// 金额格式化:保留两位小数 + 千分位
|
||||
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
@@ -119,6 +120,8 @@ export default function Termination() {
|
||||
const [filterStatus, setFilterStatus] = useState('')
|
||||
const [filterDepartment, setFilterDepartment] = useState('')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [draftPage, setDraftPage] = useState(1)
|
||||
const [draftPageSize, setDraftPageSize] = useState(20)
|
||||
|
||||
const { data: employees } = useQuery<RosterEmployee[]>({
|
||||
queryKey: ['roster-for-termination'],
|
||||
@@ -250,10 +253,10 @@ export default function Termination() {
|
||||
})
|
||||
|
||||
// 草稿列表
|
||||
const { data: drafts, refetch: refetchDrafts } = useQuery({
|
||||
queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm],
|
||||
const { data: draftsData, refetch: refetchDrafts } = useQuery({
|
||||
queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm, draftPage, draftPageSize],
|
||||
queryFn: async () => {
|
||||
const params: any = {}
|
||||
const params: any = { page: draftPage, pageSize: draftPageSize }
|
||||
if (filterStatus) params.status = filterStatus
|
||||
if (filterDepartment) params.department = filterDepartment
|
||||
if (searchTerm) params.search = searchTerm
|
||||
@@ -262,6 +265,8 @@ export default function Termination() {
|
||||
},
|
||||
enabled: view === 'list',
|
||||
})
|
||||
const drafts = draftsData?.items || []
|
||||
const draftsTotal = draftsData?.total || 0
|
||||
|
||||
// 草稿详情
|
||||
const { data: draftDetail } = useQuery({
|
||||
@@ -823,6 +828,13 @@ export default function Termination() {
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={draftPage}
|
||||
pageSize={draftPageSize}
|
||||
total={draftsTotal}
|
||||
onPageChange={setDraftPage}
|
||||
onPageSizeChange={(s) => { setDraftPageSize(s); setDraftPage(1) }}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
import { Input, Label, Select } from '../components/ui/Input'
|
||||
import Modal from '../components/ui/Modal'
|
||||
import Pagination from '../components/ui/Pagination'
|
||||
|
||||
const PROCESS_ICONS: Record<string, any> = {
|
||||
HIRE: UserPlus, ONBOARD: LogIn, CUSTOM_CONTRACT: FileSignature,
|
||||
@@ -145,11 +146,13 @@ export default function WorkProcess() {
|
||||
const [filterStatus, setFilterStatus] = useState('')
|
||||
const [detailId, setDetailId] = useState<string | null>(null)
|
||||
const [previewContent, setPreviewContent] = useState<string | null>(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
|
||||
const { data: listData, isLoading } = useQuery({
|
||||
queryKey: ['work-processes', filterType, filterStatus],
|
||||
queryKey: ['work-processes', filterType, filterStatus, page, pageSize],
|
||||
queryFn: async () => {
|
||||
const params: any = {}
|
||||
const params: any = { page, pageSize }
|
||||
if (filterType) params.type = filterType
|
||||
if (filterStatus) params.status = filterStatus
|
||||
const res = await api.get('/work-processes', { params }) as any
|
||||
@@ -238,6 +241,7 @@ export default function WorkProcess() {
|
||||
}
|
||||
|
||||
const items = listData?.items || []
|
||||
const total = listData?.total || 0
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -325,6 +329,13 @@ export default function WorkProcess() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 创建/编辑弹窗 */}
|
||||
|
||||
Reference in New Issue
Block a user