完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -37,6 +37,7 @@ import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { EmployeeAvatar } from "../components/common/EmployeeAvatar";
|
||||
import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog";
|
||||
import { AttachmentDeleteConfirmDialog } from "../components/common/AttachmentDeleteConfirmDialog";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { EmployeePerformancePanel } from "../components/people/EmployeePerformancePanel";
|
||||
import { EmployeeSalaryPanel } from "../components/people/EmployeeSalaryPanel";
|
||||
@@ -184,6 +185,8 @@ export function EmployeeProfilePage() {
|
||||
});
|
||||
const canManageEmployeeProfile = hasPermission(permissions, "system.users");
|
||||
const canManageSensitiveHr = hasPermission(permissions, "system.settings");
|
||||
const canManageEmployeeContracts = hasPermission(permissions, "employee.contract.manage");
|
||||
const canManagePayroll = hasPermission(permissions, "payroll.manage");
|
||||
const canEditSelfProfile = me?.id === employeeId;
|
||||
const canEditBaseFields = canManageEmployeeProfile || canEditSelfProfile;
|
||||
const canEditExtendedFields = canManageEmployeeProfile;
|
||||
@@ -215,7 +218,7 @@ export function EmployeeProfilePage() {
|
||||
});
|
||||
|
||||
const contractsQuery = useQuery({
|
||||
enabled: Boolean(employeeId) && canManageEmployeeProfile,
|
||||
enabled: Boolean(employeeId) && canManageEmployeeContracts,
|
||||
queryKey: ["employee-contracts", employeeId],
|
||||
queryFn: () => fetchEmployeeContracts(employeeId),
|
||||
});
|
||||
@@ -267,7 +270,7 @@ export function EmployeeProfilePage() {
|
||||
}, [employeeId, requestedTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === "salary" && !canManageSensitiveHr && !canEditSelfProfile) {
|
||||
if (activeTab === "salary" && !canManagePayroll && !canEditSelfProfile) {
|
||||
updateActiveTab("profile");
|
||||
return;
|
||||
}
|
||||
@@ -275,11 +278,11 @@ export function EmployeeProfilePage() {
|
||||
updateActiveTab("profile");
|
||||
return;
|
||||
}
|
||||
if (canManageEmployeeProfile || canEditSelfProfile || activeTab === "profile") {
|
||||
if (canManageEmployeeProfile || canManageEmployeeContracts || canEditSelfProfile || activeTab === "profile") {
|
||||
return;
|
||||
}
|
||||
updateActiveTab("profile");
|
||||
}, [activeTab, canEditSelfProfile, canManageEmployeeProfile, canManageSensitiveHr]);
|
||||
}, [activeTab, canEditSelfProfile, canManageEmployeeContracts, canManageEmployeeProfile, canManagePayroll, canManageSensitiveHr]);
|
||||
|
||||
useEffect(() => {
|
||||
setAvatarFile(null);
|
||||
@@ -464,10 +467,11 @@ export function EmployeeProfilePage() {
|
||||
});
|
||||
|
||||
const deleteContractMutation = useMutation({
|
||||
mutationFn: (contractId: string) => deleteEmployeeContract(employeeId, contractId),
|
||||
onSuccess: async (_result, contractId) => {
|
||||
mutationFn: ({ contractId, reason }: { contractId: string; reason: string }) => deleteEmployeeContract(employeeId, contractId, reason),
|
||||
onSuccess: async (_result, variables) => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["employee-contracts", employeeId] });
|
||||
if (editingContractId === contractId) {
|
||||
setDeleteContractTarget(null);
|
||||
if (editingContractId === variables.contractId) {
|
||||
setEditingContractId(null);
|
||||
setContractForm(buildEmptyContractForm());
|
||||
setContractAttachmentFile(null);
|
||||
@@ -538,7 +542,7 @@ export function EmployeeProfilePage() {
|
||||
const performanceOnlyMode = activeTab === "performance" && profilePermissionDenied && Boolean(performanceQuery.data);
|
||||
const showPerformanceTab =
|
||||
canManageSensitiveHr || canEditSelfProfile || performanceOnlyMode || performanceQuery.data?.viewerRole === "manager";
|
||||
const showSalaryTab = canManageSensitiveHr || canEditSelfProfile;
|
||||
const showSalaryTab = canManagePayroll || canEditSelfProfile;
|
||||
|
||||
const performanceContent =
|
||||
performanceQuery.isLoading ? (
|
||||
@@ -555,7 +559,7 @@ export function EmployeeProfilePage() {
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const salaryContent = <EmployeeSalaryPanel employeeId={employeeId} canManage={canManageSensitiveHr} isSelf={canEditSelfProfile} />;
|
||||
const salaryContent = <EmployeeSalaryPanel employeeId={employeeId} canManage={canManagePayroll} isSelf={canEditSelfProfile} />;
|
||||
|
||||
if (profileQuery.isLoading || (activeTab === "performance" && profilePermissionDenied && performanceQuery.isLoading)) {
|
||||
return <div className="card-std p-6 text-sm text-zinc-500">加载员工档案中...</div>;
|
||||
@@ -737,7 +741,7 @@ export function EmployeeProfilePage() {
|
||||
<Tabs value={activeTab} onValueChange={(value) => updateActiveTab(value as ProfileTab)}>
|
||||
<TabsList className="flex w-full flex-wrap justify-start gap-1">
|
||||
<TabsTrigger value="profile">档案</TabsTrigger>
|
||||
{canManageEmployeeProfile ? <TabsTrigger value="contracts">合同</TabsTrigger> : null}
|
||||
{canManageEmployeeContracts ? <TabsTrigger value="contracts">合同</TabsTrigger> : null}
|
||||
{canManageEmployeeProfile ? <TabsTrigger value="trainingCertificates">培训与证书</TabsTrigger> : null}
|
||||
{showPerformanceTab ? <TabsTrigger value="performance">绩效</TabsTrigger> : null}
|
||||
{showSalaryTab ? <TabsTrigger value="salary">薪资</TabsTrigger> : null}
|
||||
@@ -1064,7 +1068,7 @@ export function EmployeeProfilePage() {
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{canManageEmployeeProfile ? (
|
||||
{canManageEmployeeContracts ? (
|
||||
<TabsContent value="contracts" className="space-y-6">
|
||||
{contractsQuery.isError ? (
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
||||
@@ -1115,7 +1119,7 @@ export function EmployeeProfilePage() {
|
||||
<TableCell className="max-w-[220px]">
|
||||
{contract.attachmentUrl ? (
|
||||
<div className="space-y-2">
|
||||
<div className="truncate text-xs text-zinc-500">{contract.attachmentPath ?? "-"}</div>
|
||||
<div className="text-xs text-zinc-500">{contract.hasAttachment ? "已上传附件" : "未上传附件"}</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -1203,7 +1207,7 @@ export function EmployeeProfilePage() {
|
||||
<div className="rounded-2xl border border-zinc-200 bg-zinc-50 p-4">
|
||||
<div className="text-sm font-medium text-zinc-900">附件管理</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
当前附件:{contracts.find((item) => item.id === editingContractId)?.attachmentPath ?? "未上传"}
|
||||
当前附件:{contracts.find((item) => item.id === editingContractId)?.hasAttachment ? "已上传" : "未上传"}
|
||||
</div>
|
||||
<div className="mt-3 grid gap-3">
|
||||
<Input
|
||||
@@ -1520,17 +1524,18 @@ export function EmployeeProfilePage() {
|
||||
deleteAvatarMutation.mutate();
|
||||
}}
|
||||
/>
|
||||
<ActionConfirmDialog
|
||||
<AttachmentDeleteConfirmDialog
|
||||
open={Boolean(deleteContractTarget)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeleteContractTarget(null);
|
||||
}}
|
||||
kind="delete"
|
||||
title="确认删除员工合同?"
|
||||
targetName={deleteContractTarget ? `合同 ${deleteContractTarget.contractNo}` : undefined}
|
||||
description="删除后该合同记录和附件引用将从员工档案中移除,生效中的合同不能删除。"
|
||||
onConfirm={async () => {
|
||||
description={deleteContractTarget ? `对象:合同 ${deleteContractTarget.contractNo}。删除后合同记录与附件引用将从员工档案移除,且无法在 OA 内恢复。` : undefined}
|
||||
pending={deleteContractMutation.isPending}
|
||||
onConfirm={async (reason) => {
|
||||
if (deleteContractTarget) {
|
||||
deleteContractMutation.mutate(deleteContractTarget.id);
|
||||
deleteContractMutation.mutate({ contractId: deleteContractTarget.id, reason });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
在新工单中引用
屏蔽一个用户